帶你讀 MySQL 原始碼:where 條件怎麼過濾記錄?
原始碼分析系列的第 3 篇文章,我們來聊聊 MySQL 是怎麼判斷一條記錄是否匹配 where 條件的。
本文內容基於 MySQL 8.0.32 原始碼。
目錄
1. 準備工作
2. 整體介紹
3. 原始碼分析
3.1 ExecuteIteratorQuery()
3.2 FilterIterator::Read()
3.3 compare_int_signed()
3.4 Arg_comparator::compare()
3.5 Item_func_gt::val_int()
3.6 Item_cond_and::val_int()
3.7 Item_func_eq::val_int()
3.8 Item_cond_or::val_int()
4. 總結
正文
1. 準備工作
建立測試表:
CREATE TABLE `t1` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`str1` varchar(255) DEFAULT '',
`i1` int DEFAULT '0',
`i2` int DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
插入測試資料:
INSERT INTO t1(str1, i1, i2) VALUES
('s1', NULL, NULL),
('s2', 20, NULL),
('s3', 30, 31),
('s4', 40, 41),
('s5', 50, 51),
('s6', 60, 61),
('s7', 70, 71),
('s8', 80, 81);
示例 SQL:
select * from t1
where i2 > 20 and (i1 = 50 or i1 = 80)
2. 整體介紹
在原始碼中,where 條件會形成樹狀結構,示例 SQL 的 where 條件結構如下:
注意:這裡的樹狀結構
不是
資料結構中的樹。
我們可以從圖中得到以下資訊:
Item_cond_and 代表 where 條件中的
and
,連線 Item_func_gt 和 Item_cond_or。Item_func_gt 代表
i2 > 20
,其中 Item_field 包含 Field_long,代表 i2 欄位,Item_int 代表整數 20。Item_cond_or 代表 where 條件中的
or
,連線兩個 Item_func_eq。第 1 個 Item_func_eq 代表
i1 = 50
,其中 Item_field 包含 Field_long,代表 i1 欄位,Item_int 代表整數 50。第 2 個 Item_func_eq 代表
i1 = 80
,其中 Item_field 包含 Field_long,代表 i1 欄位,Item_int 代表整數 80。
接下來,我們結合堆疊來看看 where 條件的實現流程:
| > mysql_execute_command(THD*, bool) sql/sql_parse.cc:4688
| + > Sql_cmd_dml::execute(THD*) sql/sql_select.cc:578
| + - > Sql_cmd_dml::execute_inner(THD*) sql/sql_select.cc:778
| + - x > Query_expression::execute(THD*) sql/sql_union.cc:1823
| + - x = > Query_expression::ExecuteIteratorQuery(THD*) sql/sql_union.cc:1770
| + - x = | > FilterIterator::Read() sql/iterators/composite_iterators.cc:79
| + - x = | + > Item_cond_and::val_int() sql/item_cmpfunc.cc:5973
| + - x = | + - > // 第 1 個 Item::val_bool()
| + - x = | + - > // 代表 i2 > 20
| + - x = | + - > Item::val_bool() sql/item.cc:218
| + - x = | + - x > Item_func_gt::val_int() sql/item_cmpfunc.cc:2686
| + - x = | + - x = > Arg_comparator::compare() sql/item_cmpfunc.h:210
| + - x = | + - x = | > Arg_comparator::compare_int_signed() sql/item_cmpfunc.cc:1826
| + - x = | + - x = | + > Item_field::val_int() sql/item.cc:3013
| + - x = | + - x = | + - > Field_long::val_int() const sql/field.cc:3763 // i2
| + - x = | + - x = | + > Item_int::val_int() sql/item.h:4934 // 20
| + - x = | + - > // 第 2 個 Item::val_bool()
| + - x = | + - > // 代表 i1 = 50 or i1 = 80
| + - x = | + - > Item::val_bool() sql/item.cc:218
| + - x = | + - x > Item_cond_or::val_int() sql/item_cmpfunc.cc:6017
| + - x = | + - x = > // 第 3 個 Item::val_bool()
| + - x = | + - x = > // 代表 i1 = 50
| + - x = | + - x = > Item::val_bool() sql/item.cc:218
| + - x = | + - x = | > Item_func_eq::val_int() sql/item_cmpfunc.cc:2493
| + - x = | + - x = | + > Arg_comparator::compare() sql/item_cmpfunc.h:210
| + - x = | + - x = | + - > Arg_comparator::compare_int_signed() sql/item_cmpfunc.cc:1826
| + - x = | + - x = | + - x > Item_field::val_int() sql/item.cc:3013
| + - x = | + - x = | + - x = > Field_long::val_int() const sql/field.cc:3763 // i1
| + - x = | + - x = | + - x > Item_int::val_int() sql/item.h:4934 // 50
| + - x = | + - x = > // 第 4 個 Item::val_bool()
| + - x = | + - x = > // 代表 i1 = 80
| + - x = | + - x = > Item::val_bool() sql/item.cc:218
| + - x = | + - x = | > Item_func_eq::val_int() sql/item_cmpfunc.cc:2493
| + - x = | + - x = | + > Arg_comparator::compare() sql/item_cmpfunc.h:210
| + - x = | + - x = | + - > Arg_comparator::compare_int_signed() sql/item_cmpfunc.cc:1826
| + - x = | + - x = | + - x > Item_field::val_int() sql/item.cc:3013
| + - x = | + - x = | + - x = > Field_long::val_int() const sql/field.cc:3763 // i1
| + - x = | + - x = | + - x > Item_int::val_int() sql/item.h:4934 // 80
FilterIterator::Read() 從儲存引擎讀取一條記錄,Item_cond_and::val_int() 判斷該記錄是否匹配 where 條件。
從堆疊中可以看到,Item_cond_and::val_int() 的下一層有兩個 Item::val_bool():
第 1 個 Item::val_bool() 代表
i2 > 20
,經過多級呼叫 Arg_comparator::compare_int_signed() 判斷記錄的 i2 欄位值是否大於
20。第 2 個 Item::val_bool() 代表
i1 = 50 or i1 = 80
。
第 2 個 Item::val_bool() 是複合條件
,它的下層還巢狀了第 3、4 個 Item::val_bool():
第 3 個 Item::val_bool() 代表
i1 = 50
,經過多級呼叫 Arg_comparator::compare_int_signed() 判斷記錄的 i1 欄位值是否等於
50。第 4 個 Item::val_bool() 代表
i1 = 80
,經過多級呼叫 Arg_comparator::compare_int_signed() 方法判斷記錄的 i1 欄位值是否等於
80。
第 3、4 個 Item::val_bool() 中只要有一個返回 true,第 2 個 Item::val_bool() 就會返回 true,表示記錄匹配 i1 = 50 or i1 = 80。
第 1、2 個 Item::val_bool() 必須都返回 true,Item_cond_and::val_int() 才會返回 1,表示記錄匹配示例 SQL 的 where 條件。
3. 原始碼分析
3.1 ExecuteIteratorQuery()
// sql/sql_union.cc
bool Query_expression::ExecuteIteratorQuery(THD *thd) {
...
{
...
for (;;) {
// 從儲存引擎讀取一條記錄
int error = m_root_iterator->Read();
DBUG_EXECUTE_IF("bug13822652_1", thd->killed = THD::KILL_QUERY;);
// 讀取出錯,直接返回
if (error > 0 || thd->is_error()) // Fatal error
return true;
// error < 0
// 表示已經讀完了所有符合條件的記錄
// 查詢結束
else if (error < 0)
break;
// SQL 被客戶端幹掉了
else if (thd->killed) // Aborted by user
{
thd->send_kill_message();
return true;
}
...
// 傳送資料給客戶端
if (query_result->send_data(thd, *fields)) {
return true;
}
...
}
}
...
}
這個方法是 select 語句的入口,屬於重量級方法,在原始碼分析的第 1 篇文章《帶你讀 MySQL 原始碼:limit, offset》中也介紹過,但是,本文示例 SQL 的執行計劃和之前不一樣,這裡有必要再介紹下。
m_root_iterator->Read() 從儲存引擎讀取一條記錄,對於示例 SQL 來說,m_root_iterator 是 FilterIterator 迭代器物件,實際執行的方法是 FilterIterator::Read()
。
3.2 FilterIterator::Read()
int FilterIterator::Read() {
for (;;) {
int err = m_source->Read();
if (err != 0) return err;
bool matched = m_condition->val_int();
if (thd()->killed) {
thd()->send_kill_message();
return 1;
}
/* check for errors evaluating the condition */
if (thd()->is_error()) return 1;
if (!matched) {
m_source->UnlockRow();
continue;
}
// Successful row.
return 0;
}
}
上面是 FilterIterator::Read() 方法的全部程式碼,程式碼量比較少,主要邏輯如下:
m_source->Read() 方法從儲存引擎讀取一條記錄,因為示例 SQL 中 t1 表的訪問方式為全表掃描
,所以 m_source 是 TableScanIterator 迭代器物件。
透過 explain 可以確認示例 SQL 中 t1 表的訪問方式為全表掃描
(type = ALL):
explain select * from t1
where i2 > 20 and (i1 = 50 or i1 = 80)\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | t1
partitions | <null>
type | ALL
possible_keys | <null>
key | <null>
key_len | <null>
ref | <null>
rows | 8
filtered | 12.5
Extra | Using where
m_source->Read() 從儲存引擎讀取一條記錄之後,m_condition->val_int() 會判斷這條記錄是否匹配 where 條件。
m_condition 代表 SQL 的 where 條件,對於示例 SQL 來說,它是 Item_cond_and 物件。
m_condition->val_int() 實際執行的方法是 Item_cond_and::val_int()
,這就是判斷記錄是否匹配示例 SQL where 條件的入口。
3.3 compare_int_signed()
// sql/item_cmpfunc.cc
int Arg_comparator::compare_int_signed() {
// 獲取 where 條件運算子左邊的值
// 例如:i2 > 20
// 獲取當前讀取記錄的 i2 欄位值
longlong val1 = (*left)->val_int();
if (current_thd->is_error()) return 0;
// where 條件運算子左邊的值不為 NULL
// 才進入 if 分支
if (!(*left)->null_value) {
// 獲取 where 條件運算子右邊的值
// 例如:i2 > 20
// val2 的值就等於 20
longlong val2 = (*right)->val_int();
if (current_thd->is_error()) return 0;
// where 條件運算子右邊的值不為 NULL
// 才進入 if 分支
if (!(*right)->null_value) {
// 到這裡,where 條件運算子左右兩邊的值都不為 NULL
// 把 where 條件的 null_value 設定為 false
if (set_null) owner->null_value = false;
// 接下來 3 行程式碼
// 比較 where 條件運算子左右兩邊的值的大小
if (val1 < val2) return -1;
if (val1 == val2) return 0;
return 1;
}
}
// 如果執行到下面這行程式碼
// 說明 where 條件運算子左右兩邊的值
// 至少有一個是 NULL
// 把 where 條件的 null_value 設定為 true
if (set_null) owner->null_value = true;
return -1;
}
我們以 id = 2、3 的兩條記錄和示例 SQL 的 where 條件 i2 > 20
為例介紹 compare_int_signed() 的邏輯:
對於 where 條件 i2 > 20,longlong val1 = (*left)->val_int()
中的 *left
表示 i2 欄位。
讀取 id = 2 的記錄:
i2 欄位值為 NULL,if (!(*left)->null_value)
條件不成立,執行流程直接來到 if (set_null) owner->null_value = true
,把 where 條件的 null_value 設定為 true,表示對於當前讀取的記錄,where 條件包含 NULL 值。
然後,return -1
,compare_int_signed() 方法執行結束。
讀取 id = 3 記錄:
i2 欄位值為 31(即 val1 = 31),if (!(*left)->null_value)
條件成立,執行流程進入該 if 分支。
對於 where 條件 i2 > 20,longlong val2 = (*right)->val_int()
中的 *right
表示大於號右邊的 20(即 val2 = 20),if (!(*right)->null_value)
條件成立,進入該 if 分支:
if (set_null) owner->null_value = false
,把 where 條件的 null_value 設定為 false,表示對於當前讀取的記錄,where 條件不包含 NULL 值。if (val1 < val2)
,val1 = 31 大於 val2 = 20,if 條件不成立。if (val1 == val2)
,val1 = 31 大於 val2 20,if 條件不成立。return 1
,因為 val1 = 31 大於 val2 = 20,返回 1,表示當前讀取的記錄匹配 where 條件 i2 > 20。
3.4 Arg_comparator::compare()
// sql/item_cmpfunc.h
inline int compare() { return (this->*func)(); }
Arg_comparator::compare() 只有一行程式碼,就是呼叫 *func
方法,比較兩個值的大小。
func 屬性儲存了用於比較兩個值大小的方法的地址,在 Arg_comparator::set_cmp_func(...)
中賦值。
對於示例 SQL 來說,where 條件中的 i1、i2 欄位型別都是 int,func 屬性儲存的是用於比較兩個整數大小的 Arg_comparator::compare_int_signed() 方法的地址。(this->*func)()
呼叫的方法就是 Arg_comparator::compare_int_signed()。
3.5 Item_func_gt::val_int()
// sql/item_cmpfunc.cc
longlong Item_func_gt::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 = 3 的記錄和示例 SQL 的 where 條件 i2 > 20 為例,介紹 Item_func_gt::val_int() 的邏輯:
i2 欄位值為 31,對 where 條件 i2 > 20 呼叫 cmp.compare(),得到的返回值為 1(即 value = 1)。
value > 0 ? 1 : 0
表示式的值為 1,這就是 Item_func_ge::val_int() 的返回值,表示 id = 3 的記錄匹配 where 條件 i2 > 20。
3.6 Item_cond_and::val_int()
// sql/item_cmpfunc.cc
longlong Item_cond_and::val_int() {
assert(fixed == 1);
// and 連線的 N 個 where 條件都儲存到 list 中
// 根據 list 構造迭代器
List_iterator_fast<Item> li(list);
Item *item;
null_value = false;
// 迭代 where 條件
while ((item = li++)) {
if (!item->val_bool()) {
if (ignore_unknown() || !(null_value = item->null_value))
return 0; // return false
}
if (current_thd->is_error()) return error_int();
}
return null_value ? 0 : 1;
}
Item_cond_and::val_int() 的邏輯:
判斷當前讀取的記錄是否匹配 Item_cond_and 物件所代表的 and 連線的 N 個 where 條件(N >= 2)。
如果對
每個
條件呼叫 item->val_bool() 的返回值都是
true,說明記錄匹配 and 連線的 N 個 where 條件。如果對
某一個或多個
條件呼叫 item->val_bool() 的返回值是 false,就說明記錄不匹配 and 連線的 N 個 where 條件。
由於 if (ignore_unknown() || !(null_value = item->null_value))
中的 ignore_unknown() 用於控制 where 條件中包含 NULL 值時怎麼處理,我們需要展開介紹 Item_cond_and::val_int() 的程式碼。
想要深入瞭解 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() 的定義:
class Item_cond : public Item_bool_func {
...
/// Treat UNKNOWN result like FALSE
/// because callers see no difference
bool ignore_unknown() const { return abort_on_null; }
...
}
從程式碼註釋可以看到,ignore_unknown() 用於決定是否把 UNKNOWN
當作 FALSE 處理。
那麼,什麼是 UNKNOWN?
在 MySQL 中,NULL 會被特殊對待。NULL 和任何值(包含 NULL 本身)透過關係運算子(=、>、<、...)比較,得到的結果都是 NULL,這個結果就被認為是 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 條件。
3.7 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。
3.8 Item_cond_or::val_int()
// 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_fast
對於 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。
4. 總結
本文介紹了 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 條件。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2948628/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MongoDB之資料查詢(where條件過濾)MongoDB
- Stream中filter過濾條件問題記錄Filter
- 資料泵過濾匯出資料的where條件
- 帶你讀 MySQL 原始碼:limit, offsetMySql原始碼MIT
- mysql,where條件查詢等學習筆記MySql筆記
- MySQL核心技術之“WHERE條件”MySql
- MYSQL學習筆記6: DQL條件查詢(where)MySql筆記
- 條件過濾檢索
- 《MySQL 入門教程》第 08 篇 過濾條件MySql
- MySQL全面瓦解7:查詢的過濾條件MySql
- 儲存過程WHERE條件不生效儲存過程
- Vue原始碼閱讀--過濾器Vue原始碼過濾器
- switch拼接where條件
- [Q]怎麼實現一條記錄根據條件多表插入 zt
- mysql多條件過濾查詢之mysq高階查詢MySql
- Percona MySQL 5.6 WHERE 條件中 OR 的索引測試MySql索引
- mysql where條件中 字串右邊的空格會忽略MySql字串
- SQL中on條件與where條件的區別[轉]SQL
- java8 多條件的filter過濾JavaFilter
- mysql 插入時帶判斷條件MySql
- MySQL的where條件字串區分大小寫的問題MySql字串
- MySQL中WHERE後跟著N多個OR條件會是你期望的結果嗎?MySql
- [原始碼]Sqlite是怎麼通過CursorWindow讀DB的原始碼SQLite
- mysql怎麼插入空記錄MySql
- ORACLE SQL過濾條件是IS NULL or !=的優化OracleSQLNull優化
- JN專案-查詢條件過濾特殊字元字元
- 自定義 Azure Table storage 查詢過濾條件
- Laravel admin grid where 條件加 orWhere filter 不起作用怎麼解決LaravelFilter
- MySQL 5.7.9 原始碼安裝記錄MySql原始碼
- Lambda表示式where過濾資料
- DB2 exists子句會過濾掉所用的本條記錄DB2
- 怎麼閱讀原始碼【除錯觀察原始碼】原始碼除錯
- RIPS原始碼閱讀記錄(二)原始碼
- Tomcat 原始碼閱讀記錄(1)Tomcat原始碼
- 直播帶貨原始碼,android editText設定顏文字過濾原始碼Android
- 關於外連線和where條件
- LINQ系列:LINQ to SQL Where條件SQL
- 34. 過濾條件、多表查詢、子查詢