热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

insert时调用本身字段_selectcount(*)执行时的底层原理

SELECTCOUNT(*)FROMTABLE是个再常见不过的SQL需求了。在MySQL的使用规范中,我们一般使用事务引擎InnoDB作为(一般业务)表的存储引擎&#

020e35fc61e46aaf7085e13ca9ec378d.png

SELECT COUNT( * ) FROM TABLE 是个再常见不过的 SQL 需求了。

在 MySQL 的使用规范中,我们一般使用事务引擎 InnoDB 作为(一般业务)表的存储引擎,在此前提下,COUNT( * )操作的时间复杂度为 O(N),其中 N 为表的行数。

而 MyISAM 表中可以快速取到表的行数。这些实践经验的背后是怎样的机制,以及为什么需要/可以是这样,就是此文想要探讨的。

先来看一下概况,MySQL COUNT( * ) 在 2 种存储引擎中的部分问题:

0f06492f95568781914050f8b0402538.png

下面就带着这些问题,以 InnoDB 存储引擎为主来进行讨论。

一、InnoDB 全表 COUNT( * )

主要问题:

1、执行过程是怎样的?

2、如何计算 count?影响 count 结果的因素有哪些?

3、count 值存在哪里?涉及的数据结构是怎样的?

4、为什么 InnoDB 只能通过扫表来实现 count( * )?(见本文最后的问题)

5、全表COUNT( * )作为 table scan 类型操作的一个 case,有什么风险?

6、COUNT(* )操作是否会像“SELECT * ”一样可能读取大字段涉及的溢出页?

1、执行框架 – 循环: 读取 + 计数?

1.1、基本结论:

  • 全表扫描,一个循环解决问题。

  • 循环内: 先读取一行,再决定该行是否计入 count。

  • 循环内是一行一行进行计数处理的。

1.2、说明:

简单 SELELCT-SQL 的执行框架,类比 INSERT INTO … SELECT 是同样的过程。

下面会逐步细化如何读取与计数 ( count++ ) 。

8ca6abbf92771a5009771aa5dc9014c9.png

2、执行过程?

执行过程部分,分为 4 个部分:

cb83523b5d4a54de542f06c30c974ddb.png

(1)COUNT( * ) 前置流程: 从 Client 端发 SQL 语句,到 MySQL-Server 端执行 SELECT 之前,为后面的一些阐述做一铺垫。

(2)COUNT( * ) 流程: 简要给出代码层面的流程框架及 2 个核心步骤的重点调用栈部分。

(3)读取一行: 可见性及 row_search_mvcc 函数,介绍可见性如何影响 COUNT( * ) 结果。

(4)计数一行: Evaluate_join_record 与列是否为空,介绍计数过程如何影响 COUNT( * ) 结果。

如果读者希望直接看如何进行 COUNT( * ),那么也可以忽略 (1),而直接跳到 (2) 开始看。

2.1、COUNT( * ) 前置流程回忆 – 从 Client 端发 SQL 到 sub_select 函数

为了使看到的调用过程不太突兀,我们还是先回忆一下如何执行到 sub_select 函数这来的:

18d4d3f2f189d308736ef70daf892a57.png

(1)MySQL-Client 端发送 SQL 语句,根据 MySQL 通信协议封包发送。

(2)Mysql-Server 端接收数据包,由协议解析出 command 类型 ( QUERY ) 及 SQL 语句 ( 字符串 ) 。

(3)SQL 语句经过解析器解析输出为 JOIN 类的对象,用于结构化地表达该 SQL 语句。

PS: 这里的 JOIN 结构,不仅仅是纯语法结构,而是已经进行了语义处理,粗略地说,汇总了表的列表 ( table_list )、目标列的列表 ( target_list )、WHERE 条件、子查询等语法结构。

在全表 COUNT( * )-case 中,table_list = [表“t”(别名也是“t”)],target_list = [目标列对象(列名为“COUNT( * )”)],当然这里没有 WHERE 条件、子查询等结构。

(4)JOIN 对象有 2 个重要的方法: JOIN::optimize(), JOIN::exec(),分别用于进行查询语句的优化 和 查询语句的执行。

  • join->optimize(),优化阶段 (稍后 myisam 下全表 count( * ) 操作会涉及这里的一点内容)。

  • join->exec(),执行阶段 ( 重点 ),包含了 InnoDB 下全表count( * ) 操作的执行流程。

(5)join->exec() 经过若干调用,将调用到 sub_select 函数来执行简单 SQL,包括 COUNT( * ) 。

(6)END of sub_select 。

2.2、COUNT( * ) 流程 ( 于 sub_select 函数中 )

上层的流程与代码是比较简单的,集中在 sub_select 函数中,其中 2 类函数分别对应于前面”执行框架”部分所述的 2 个步骤 – 读取、计数。先给出结论如下:

(1)读取一行:从相对顶层的 sub_select 函数经过一番调用,最终所有分支将调用到 row_search_mvcc 函数中,该函数就是用于从 InnoDB 存储引擎所存储的 B+-tree 结构中读取一行到内存中的一个 buf (uchar * ) 中,待后续处理使用。

这里会涉及行锁的获取、MVCC 及行可见性的问题。当然对 于 SELECT COUNT( * ) 这类快照读而言,只会涉及 MVCC 及其可见性,而不涉及行锁。详情可跳至“可见性与 row_search_mvcc 函数”部分。

(2)计数一行: 代码层面,将会在 evaluate_join_record 函数中对所读取的行进行评估,看其是否应当计入 count 中 ( 即是否要 count++ )。

简单来说,COUNT(arg) 本身为 MySQL 的函数操作,对于一行来说,若括号内的参数 arg ( 某列或整行 ) 的值若不是 NULL,则 count++,否则对该行不予计数。详情可跳至“ Evaluate_join_record 与列是否为空”部分。

这两个阶段对 COUNT( * )结果的影响如下: (两层过滤)

SQL 层流程框架相关代码摘要如下:

23c45e84e0919d5d6a7cb0ce0e194c21.png

Q:代码层面,第一步骤(读取一行)有 2 个分支,为什么?

A:从 InnoDB 接口层面考虑,分为 “读第一行” 和 “读下一行”,是 2 个不同的执行过程,读第一行需要找到一个 ( cursor ) 位置并做一些初始化工作让后续的过程可递归。

正如我们如果用脚本/程序来进行逐行的扫表操作,实现上就会涉及下面 2 个 SQL:

9d017a9e481e04bb74ede3dea422db9e.png

具体涉及到此例的代码,SQL 层到存储引擎层的调用关系,读取阶段的调用栈如下:(供参考)

899c3a9e88682e9dd460860dd4214062.png

我们可以看到,无论是哪一个分支的读取,最终都殊途同归于 row_search_mvcc 函数。

以上是对 LOOP 中的代码做一些简要的说明,下面来看 row_search_mvcc 与 evaluate_join_record 如何输出最终的 count 结果。

2.3、行可见性及 row_search_mvcc 函数

这里我们主要通过一组 case 和几个问题来看行可见性对 COUNT( * ) 的影响。

f9fe31fc15bcf8338fbeb2890ddd4383.png

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。

  • e.g. SELECT COUNT(col_name) FROM t

  • col_name 可以是主键、唯一键、非唯一键、非索引字段

(2)如果 COUNT 中带有 * ,则会判断这部分的整行是否为 NULL,如果判断参数为 NULL,则忽略该行,否则 count++。

  • e.g-1. SELECT COUNT(*) FROM t

  • e.g-2. SELECT COUNT(B.*) FROM A LEFT JOIN B ON A.id = B.id

Q:特别地,对于 SELECT COUNT(id) FROM t,其中 id 字段是表 t 的主键,则如何?

A:效果上等价于 COUNT( * )。因为无论是 COUNT( * ),还是 COUNT ( pk_col ) 都是因为有主键从而充分断定索取数据不为 NULL,这类 COUNT 表达式可以用于获取当前可见的表行数。

Q:用户层面对 InnoDB COUNT( * ) 的优化操作问题

A:这个问题是业界熟悉的一个问题,扫描非空唯一键可得到表行数,但所涉及的字节数可能会少很多(在表的行长与主键、唯一键的长度相差较多时),相对的 IO 代价小很多。

相关调用栈参考如下:

edfa4a1ec8dd2db8288a598677457857.png

二、数据结构

Q:count 值存储在哪个内存变量里?

A:SQL 解析后,存储于表达 COUNT( * ) 这一项中,((Item_sum_count*)item_sum)->count

如下图所示回顾我们之前“COUNT( * )前置流程”部分提到的 JOIN 结构。

eab372042480ca46b04e89fe556feb37.png

即 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 引擎并不常用于实际业务中,仅做简要描述如下:

1、MyISAM-COUNT( * ) 操作是 O(1) 时间复杂度的操作。

2、每张 MyISAM 表中存放了一个 meta 信息-count 值,在内存中与文件中各有一份,内存中的 count 变量值通过读取文件中的 count 值来进行初始化。

3、SELECT COUNT( * ) FROM t 会直接读取内存中的表 t 对应的 count 变量值。

4、内存中的 count 值与文件中的 count 值由写操作来进行更新,其一致性由表级锁来保证。

5、表级锁保证的写入串行化使得,同一时刻所有用户线程的读操作要么被锁,要么只会看到一种数据状态。

四、几个问题

Q:MyISAM 与 InnoDB 在 COUNT( * ) 操作的执行过程在哪里开始分道扬镳?

  • 共性:

    共性存在于 SQL 层,即 SQL 解析之后的数据结构是一致的,count 变量都是存在于作为结果列的 Item_sum_count 类型对象中;

    返回给客户端的过程也类似 – 对该 count 变量进行赋值并经由 MySQL 通信协议返回给客户端。

  • 区别:InnoDB 的 count 值计算是在 SQL 执行阶段进行的;而 MyISAM 表本身在内存中有一份包含了表 row_count 值的 meta 信息,在 SQL 优化阶段通过存储引擎的标记给优化器一个 hint,表明该表所用的存储引擎保存了精确行数,可以直接获取到,无需再进入执行器。

dc97cc9ba7ac63145a27b2788be2d3ec.png

原文链接:blog.didiyun.com/index.php/2019/01/08/mysql-count

088d5795c1327d9209af1ab862fe3f19.png

如果感觉推送内容不错,不妨右下角点个在看,感谢支持!




推荐阅读
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Postgresql备份和恢复的方法及命令行操作步骤
    本文介绍了使用Postgresql进行备份和恢复的方法及命令行操作步骤。通过使用pg_dump命令进行备份,pg_restore命令进行恢复,并设置-h localhost选项,可以完成数据的备份和恢复操作。此外,本文还提供了参考链接以获取更多详细信息。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
author-avatar
从容面对天下事
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有