我们来聊聊 MySQL 是怎么判断一条记录是否匹配 where 条件的。
本文内容基于 MySQL 8.0.32 源码。
创建测试表:
想要深入了解 Item_cond_and::val_int() 代码细节的读者朋友,可以做个心理建设:内容有点长(但不会太长)。首先,我们来看一下 null_value = false:null_value 的初始值被设置为 false,表示 and 连接的 N 个 where 条件中,还没出现哪个 where 条件包含 NULL 值的情况(毕竟还啥都没干)。null_value 比较重要,它有可能最终决定 Item_cond_and::val_int() 的返回值(后面会介绍)。然后,再来看看 while 循环的逻辑,这块内容会有一点点多:while 循环迭代 and 连接的 N 个 where 条件。每迭代一个 where 条件,都调用 item->val_bool() 方法,判断当前读取的记录是否匹配该条件。如果 val_bool() 的返回值是 true,说明记录匹配该条件,进入下一轮循环,迭代下一个 where 条件(如果有的话)。if (current_thd->is_error()),这行代码表示执行过程中出现了错误,我们先忽略它。如果 val_bool() 的返回值是 false,说明记录不匹配该条件。接下来是进入下一轮循环,还是执行 return 0 结束 Item_cond_and::val_int() 方法,就要由 if (ignore_unknown() || !(null_value = item->null_value)) 决定了。展开介绍 if (ignore_unknown() || ...) 之前,先来看看 ignore_unknown() 的定义:如果想知道某个值是否为 NULL,只能使用 IS NULL、IS NOT NULL 进行判断。说完了 ignore_unknown(),我们回到 if (ignore_unknown() || !(null_value = item->null_value)),它包含两个表达式:ignore_unknown() !(null_value = item->null_value)) 如果 ignore_unknown() 的返回值为 true,if 条件成立,执行流程就会进入 if 分支,执行 return 0,Item_cond_and::val_int() 方法的执行流程就此结束,表示当前读取的记录不匹配 and 连接的 N 个 where 条件。如果 ignore_unknown() 的返回值为 false,那么还需要再判断 !(null_value = item->null_value)) 的值是 true 还是 false。我们先分解一下 !(null_value = item->null_value)),其中包含 2 个步骤:null_value = item->null_value !null_value 如果 item->null_value 的值为 false,赋值给 null_value 之后,!null_value 的值为 true,if 条件成立,执行流程就会进入 if (ignore_unknown() || ...) 分支,执行 return 0,Item_cond_and::val_int() 方法的执行流程就此结束,表示当前读取的记录不匹配 and 连接的 N 个 where 条件。item->null_value = false,表示对于当前读取的记录,where 条件不包含 NULL 值。如果 item->null_value 的值为 true,赋值给 null_value 之后,!null_value 的值为 false,即 !(null_value = item->null_value)) 的值为 false,if 条件不成立,执行流程不会进入 if (ignore_unknown() || ...) 分支,也就不会执行 return 0 了,接下来就会进入下一轮循环,迭代下一个 where 条件(如果有的话)。item->null_value = true,表示对于当前读取的记录,where 条件包含 NULL 值。最后,再来看看 return null_value ? 0 : 1:while 循环迭代完 and 连接的 N 个 where 条件之前,如果 Item_cond_and::val_int() 方法的执行流程都没有被 while 代码块中包含的 return 0 提前结束,执行流程就会来到 return null_value ? 0 : 1。有两种场景会导致这种情况的出现:场景 1:while 循环迭代 and 连接的 N 个 where 条件的过程中,对每个条件调用 item->val_bool() 的返回值都是 true。此时,null_value 属性的值为 false,null_value ? 0 : 1 表达式的值为 1,说明当前读取的记录匹配 and 连接的 N 个 where 条件。场景 2:while 循环迭代 and 连接的 N 个 where 条件的过程中,某个条件同时满足以下 4 个要求:调用 item->val_bool() 的返回值是 false,说明当前读取的记录不匹配该条件。ignore_unknown() 的返回值也是 false,表示包含 NULL 值的 where 条件的比较结果(UNKNOWN)不按 false 处理,而是要等到 while 循环结束之后,根据 null_value 属性的值(true 或 false)算总帐。这是由 Item_cond_and 对象控制的行为,而不是 and 连接的某个 where 条件控制的行为。!(null_value = item->null_value)) 表达式的值为 false,说明该条件包含 NULL 值,那么它就是 ignore_unknown() = false 时需要等到 while 循环结束之后,根据 null_value 属性的值算总帐的条件。该条件之后的其它 where 条件,不会导致 while 循环被提前中止(这样执行流程才能来到 return null_value ? 0 : 1)。此时,null_value 属性的值为 true,null_value ? 0 : 1 表达式的值为 0,说明当前读取的记录不匹配 and 连接的 N 个 where 条件。 Item_func_eq::val_int()// sql/item_cmpfunc.cc longlong Item_func_eq::val_int() { assert(fixed == 1); int value = cmp.compare(); return value == 0 ? 1 : 0; }
这里调用的 cmp.compare() 就是前面介绍的 Arg_comparator::compare() 方法。
对于示例 SQL 来说,Arg_comparator::compare() 调用的是 Arg_comparator::compare_int_signed() 方法,返回值只有 3 种:
-1:表示 where 条件操作符左边的值小于右边的值。 0:表示 where 条件操作符左边的值等于右边的值。 1:表示 where 条件操作符左边的值大于右边的值。我们以 id = 5 的记录和示例 SQL 的 where 条件 i1 = 50 为例,介绍 Item_func_eq::val_int() 的逻辑:
i1 字段值为 50,对 where 条件 i1 = 50 调用 cmp.compare(),得到的返回值为 0(即 value = 0)。
value == 0 ? 1 : 0 表达式的值为 1,这就是 Item_func_eq::val_int() 的返回值,表示 id = 5 的记录匹配 where 条件 i1 = 50。
// sql/item_cmpfunc.cc
longlong Item_cond_or::val_int() {
assert(fixed == 1);
List_iterator_fast<Item> li(list);
Item *item;
null_value = false;
while ((item = li++)) {
if (item->val_bool()) {
null_value = false;
return 1;
}
if (item->null_value) null_value = true;
...
}
return 0;
}
我们以 id = 8 的记录和示例 SQL 的 where 条件 i1 = 50 or i1 = 80 为例,介绍 Item_cond_or::val_int() 的逻辑:
Item_cond_or 对象的 list 属性包含 2 个条件:i1 = 50、i1 = 80,List_iterator_fastli(list) 根据 list 构造一个迭代器。
对于 id = 8 的记录,i1 字段值为 80,while 循环每次迭代一个 where 条件:
第 1 次迭代,对 where 条件 i1 = 50 调用 item->val_bool(),返回值为 false,不进入 if (item->val_bool()) 分支。
if (item->null_value) 条件不成立,不执行 null_value = true。
第 2 次迭代,对 where 条件 i1 = 80 调用 item->val_bool(),返回值为 true,进入 if (item->val_bool()) 分支。
设置 Item_cond_or 对象的 null_value 属性值为 false,表示 Item_cond_or 所代表的 or 连接的 where 条件(i1 = 50、i1 = 80)都不包含 NULL 值。
return 1,这就是 Item_cond_or::val_int() 的返回值,表示 id = 8 的记录匹配 where 条件 i1 = 50 or i1 = 80。
本文介绍了 SQL 的 where 条件中包含 and、or 的实现逻辑:
从存储引擎读取一条记录之后,对 and 连接的 N 个 where 条件(N >= 2)调用 item->val_bool() 的返回值必须全部等于 true,记录才匹配 and 连接的 N 个 where 条件。
Item_cond_and::val_int() 的代码不多,但是这个方法中调用了 ignore_known() 用于控制怎么处理 where 条件包含 NULL 值的场景,代码细节并不太好理解,所以花了比较长的篇幅介绍 Item_cond_and::val_int() 方法的逻辑,需要多花点时间去理解其中的逻辑。
从存储引擎读取一条记录之后,对 or 连接的 N 个 where 条件(N >= 2)调用 item->val_bool(),只要其中一个返回值等于 true,记录就匹配 or 连接的 N 个 where 条件。
本文转载自微信公众号「一树一溪」,可以通过以下二维码关注。转载本文请联系一树一溪公众号。