最近有人问我MySQL中一个字段不论=NULL还是<>NULL都匹配不了数据,是在框架层实现的还是在存储引擎层实现的,我说你看看如果InnoDB表和MyISAM表都有这个现象,那就比较可能是在框架层。
当然这个答案跟没有回答一样,我们可以从代码上看看具体的实现部分。
1、 现象描述
表结构
CREATE TABLE `t` ( `c` char(32) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=gbk |
插入两行数据
insert into t (c) values('a'),(null); |
查询
mysql> select * from t where c=null; Empty set (0.00 sec) mysql> select * from t where c<>null; Empty set (0.00 sec) mysql> select * from t where c is not null; +------+ | c | +------+ | a | +------+ 1 row in set (0.00 sec) |
说明:从上面的三个查询可以看出,使用=null和<>null都无法返回数据,只能通过is或is not 来比较。
2、代码相关
我们知道大概的流程,是引擎返回索引过滤后的结果,在框架层再依次判断记录是否符合条件。判断条件是否满足是在函数evaluate_join_record (sql_select.cc)中。
if (select_cond)
{
select_cond_result= test(select_cond->val_int()); /* check for errors evaluating the condition */
if (join->thd->is_error())
return NESTED_LOOP_ERROR;
}
if (!select_cond || select_cond_result)
{ ... }
第三行的select_cond->val_int(),就是判断where的条件是否相同。其中select_cond的类型Item是一个虚类。我们从val_int()的函数注释中看到这样一句话“In case of NULL value return 0 and set null_value flag to TRUE.”,确认了这个是在框架层作的,而且是有意为之。
一路追查到这个函数int Arg_comparator::compare_string (sql/item_cmpfunc.cc),这个函数是用语判断两个字符串是否相同。
int Arg_comparator::compare_string() { String *res1,*res2; if ((res1= (*a)->val_str(&value1))) { if ((res2= (*b)->val_str(&value2))) { if (set_null) owner->nullvalue= 0; return sortcmp(res1,res2,cmp_collation.collation); } } if (set_null) owner->nullvalue= 1; return -1; } |
函数返回值为0时表示相同,否则不同。
其中a是左值,b是右值。即如果输入的是 where ‘i’=c, 则a的值是’i’。从第4行和第6行的两个if看到,当a和b中有一个是null的时候,不进入sortcmp,而是直接return -1。
3、验证修改
声明:这个只是为了验证结论修改,纯属练手,实际上现有的策略并没有问题。
int Arg_comparator::compare_string()
{
String *res1,*res2;
res1= (*a)->val_str(&value1);
res2= (*b)->val_str(&value1);
if (!res1 && !res2)
{
return 0;
}
else if ((!res1 && res2) || (res1 && !res2))
{
return 1;
}
else
{
return sortcmp(res1,res2,cmp_collation.collation);
}
}
重新编译后执行效果如下
mysql> select * from t where c=null; +--------------+ | c | +--------------+ | NULL | +--------------+ 1 row in set (0.00 sec) mysql> select * from t where c<>null; +--------------+ | c | +--------------+ | a | +--------------+ 1 row in set (0.00 sec) |
记得改回去。。。 ^_^
4、相关说明
a) Arg_comparator::compare_string() 这个函数只用于定义两个char[]的判断规则,因此修改后的执行程序中,非字符串字段判断仍为原来的规则,即=null和<>null都认为不匹配。
b) 标准判断是否为null的用法是 where c is null和is not null。此时使用的判断函数为Item_func_isnull::val_int()和Item_func_isnotnull::val_int() , 这两个函数比较简单,直接用args[0]->is_null()判断。