深入理解select count(*)底层究竟做了什么
正如我们如果用脚本/程序来进行逐行的扫表操作,实现上就会涉及下面 2 个 SQL:
具体涉及到此例的代码,SQL 层到存储引擎层的调用关系,读取阶段的调用栈如下:(供参考) 我们可以看到,无论是哪一个分支的读取,最终都殊途同归于 row_search_mvcc函数。 以上是对 LOOP 中的代码做一些简要的说明,下面来看 row_search_mvcc与 evaluate_join_record 如何输出最终的 count 结果。 2.3 行可见性及 row_search_mvcc 函数 这里我们主要通过一组 case 和几个问题来看行可见性对 COUNT( * ) 的影响。 Q:对于SELECT COUNT( * ) FROM t或者SELECT MIN(id) FROM t操作,第一次的读行操作读到的是表 t 中 ( B+ 树最左叶节点 page 内 ) 的最小记录吗?( ha_index_first 为何也调用 row_search_mvcc 来获取最小 key 值?) A:不一定。即使是MIN ( id ) 也不一定就读取的是 id 最小的那一行,因为也同样有行可见性的问题,实际上 index_read 取到的是 当前事务内语句可见的最小 index 记录。这也反映了前面提到的 join_read_first 与 join_read_next “殊途同归”到 row_search_mvcc 是理所应当的。 Q:针对图中最后一问,如果事务 X 是 RU ( Read-Uncommitted ) 隔离级别,且 C-Insert ( 100 ) 的完成是在 X-count( * )执行过程中 ( 仅扫描到 5 或 10 这条记录 ) 完成的,那么 X-count( * ) 在事务 C-Insert ( 100 ) 完成后,能否在之后的读取过程中看到 100 这条记录呢? A:MySQL 采取”读到什么就是什么”的策略,即X-count( * )在后面可以读到 100 这条记录。 2.4 evaluate_join_record 与列是否为空 Q:某一行如何计入 count? A:两种情况会将所读的行计入 count: 1、如果 COUNT 函数中的参数是某列,则会判断所读行中该列定义是否 Nullable以及该列的值是否为 NULL;若两者均为是,则不会计入 count,否则将计入 count。
2、如果 COUNT 中带有 * ,则会判断这部分的整行是否为 NULL,如果判断参数为 NULL,则忽略该行,否则 count++。
Q: 特别地,对于 SELECT COUNT(id) FROM t,其中 id 字段是表 t 的主键,则如何? A:效果上等价于 COUNT( * )。因为无论是 COUNT( * ),还是 COUNT ( pk_col ) 都是因为有主键从而充分断定索取数据不为 NULL,这类 COUNT 表达式可以用于获取当前可见的表行数。 Q: 用户层面对 InnoDB COUNT( * ) 的优化操作问题 A:这个问题是业界熟悉的一个问题,扫描非空唯一键可得到表行数,但所涉及的字节数可能会少很多(在表的行长与主键、唯一键的长度相差较多时),相对的 IO 代价小很多。 相关调用栈参考如下: 二、数据结构: Q:count 值存储在哪个内存变量里? A:SQL 解析后,存储于表达 COUNT( * ) 这一项中,((Item_sum_count*)item_sum)->count 如下图所示回顾我们之前“COUNT( * )前置流程”部分提到的 JOIN 结构。 即 SQL 解析器为每个 SQL 语句进行结构化,将其放在一个 JOIN 对象 ( join ) 中来表达。在该对象中创建并填充了一个列表 result_field_list 用于存放结果列,列表中每个元素则是一个结果列的 ( Item_result_field*) 对象 ( 指针 ) 。 在 COUNT( * )-case 中,结果列列表只包含一个元素,( Item_sum_count: public Item_result_field ) 类型对象 ( name = “COUNT( * )”),其中该类所特有的成员变量 count即为所求。 三、MyISAM 全表 COUNT( * ) 由于 MyISAM引擎并不常用于实际业务中,仅做简要描述如下:
四、几个问题 (编辑:晋中站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |