我们来聊聊 MySQL 是过滤怎么判断一条记录是否匹配 where 条件的。
本文内容基于 MySQL 8.0.32 源码。记录
创建测试表:
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 t1where i2 > 20 and (i1 = 50 or i1 = 80)
在源码中,where 条件会形成树状结构,过滤示例 SQL 的记录 where 条件结构如下:
注意:这里的树状结构不是数据结构中的树。
我们可以从图中得到以下信息:
接下来,我们结合堆栈来看看 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():
第 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 条件。
// sql/sql_union.ccbool 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()。
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 t1where i2 > 20 and (i1 = 50 or i1 = 80)\G(责任编辑:探索)
魅族魅蓝note3 PK 红米note3 两款千元智能机性价比大比拼
“海基一号”平台主体工程海上安装完成 处于南海内波流主通道上
继FPA、K歌房后 声网发布融合CDN直播等系列新品 拓宽RTE产品边界
棠记控股(08305.HK)预计年度亏损不少于50万港元 毛利严重下降