MySQL中NULL欄位的比較問題

丁奇發表於2016-03-24

最近有人問我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都無法返回資料,只能通過isis 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看到,當ab中有一個是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 nullis not null。此時使用的判斷函式為Item_func_isnull::val_int()Item_func_isnotnull::val_int() 這兩個函式比較簡單,直接用args[0]->is_null()判斷


相關文章