`

一张900w的数据表,16s执行的SQL优化到300ms?

 
阅读更多
有一张财务流水表,未分库分表,目前的数据量为9555695,分页查询使用到了limit,优化之前的查询耗时16 s 938 ms (execution: 16 s 831 ms, fetching: 107 ms),按照下文的方式调整SQL后,耗时347 ms (execution: 163 ms, fetching: 184 ms);
操作:查询条件放到子查询中,子查询只查主键ID,然后使用子查询中确定的主键关联查询其他的属性字段;

原理:减少回表操作;

-- 优化前SQL
SELECT  各种字段
FROM `table_name`
WHERE 各种条件
LIMIT 0,10;

-- 优化后SQL
SELECT  各种字段
FROM `table_name` main_tale
RIGHT JOIN
(
SELECT  子查询只查主键
FROM `table_name`
WHERE 各种条件
LIMIT 0,10;
) temp_table ON temp_table.主键 = main_table.主键

一,前言

首先说明一下MySQL的版本:

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.17    |
+-----------+
1 row in set (0.00 sec)
表结构:

mysql> desc test;
+--------+---------------------+------+-----+---------+----------------+
| Field  | Type                | Null | Key | Default | Extra          |
+--------+---------------------+------+-----+---------+----------------+
| id     | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| val    | int(10) unsigned    | NO   | MUL | 0       |                |
| source | int(10) unsigned    | NO   |     | 0       |                |
+--------+---------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
id为自增主键,val为非唯一索引。

灌入大量数据,共500万:

mysql> select count(*) from test;
+----------+
| count(*) |
+----------+
|  5242882 |
+----------+
1 row in set (4.25 sec)
我们知道,当limit offset rows中的offset很大时,会出现效率问题:

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id      | val | source |
+---------+-----+--------+
| 3327622 |   4 |      4 |
| 3327632 |   4 |      4 |
| 3327642 |   4 |      4 |
| 3327652 |   4 |      4 |
| 3327662 |   4 |      4 |
+---------+-----+--------+
5 rows in set (15.98 sec)
为了达到相同的目的,我们一般会改写成如下语句:

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id      | val | source | id      |
+---------+-----+--------+---------+
| 3327622 |   4 |      4 | 3327622 |
| 3327632 |   4 |      4 | 3327632 |
| 3327642 |   4 |      4 | 3327642 |
| 3327652 |   4 |      4 | 3327652 |
| 3327662 |   4 |      4 | 3327662 |
+---------+-----+--------+---------+
5 rows in set (0.38 sec)
时间相差很明显。

为什么会出现上面的结果?我们看一下select * from test where val=4 limit 300000,5;的查询过程:

查询到索引叶子节点数据。

根据叶子节点上的主键值去聚簇索引上查询需要的全部字段值。

类似于下面这张图:



像上面这样,需要查询300005次索引节点,查询300005次聚簇索引的数据,最后再将结果过滤掉前300000条,取出最后5条。MySQL耗费了大量随机I/O在查询聚簇索引的数据上,而有300000次随机I/O查询到的数据是不会出现在结果集当中的。

肯定会有人问:既然一开始是利用索引的,为什么不先沿着索引叶子节点查询到最后需要的5个节点,然后再去聚簇索引中查询实际数据。这样只需要5次随机I/O,类似于下面图片的过程:



其实我也想问这个问题。

证实

下面我们实际操作一下来证实上述的推论:

为了证实select * from test where val=4 limit 300000,5是扫描300005个索引节点和300005个聚簇索引上的数据节点,我们需要知道MySQL有没有办法统计在一个sql中通过索引节点查询数据节点的次数。我先试了Handler_read_*系列,很遗憾没有一个变量能满足条件。

我只能通过间接的方式来证实:

InnoDB中有buffer pool。里面存有最近访问过的数据页,包括数据页和索引页。所以我们需要运行两个sql,来比较buffer pool中的数据页的数量。预测结果是运行select * from test a inner join (select id from test where val=4 limit 300000,5); 之后,buffer pool中的数据页的数量远远少于select * from test where val=4 limit 300000,5;对应的数量,因为前一个sql只访问5次数据页,而后一个sql访问300005次数据页。

select * from test where val=4 limit 300000,5

mysql> select index_name,count(*) from
information_schema.INNODB_BUFFER_PAGE where
INDEX_NAME in('val','primary') and TABLE_NAME like '%test%'
group by index_name;Empty set (0.04 sec)
可以看出,目前buffer pool中没有关于test表的数据页。

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id      | val | source |
+---------+-----+--------+|
3327622 |   4 |      4 |
| 3327632 |   4 |      4 |
| 3327642 |   4 |      4 |
| 3327652 |   4 |      4 |
| 3327662 |   4 |      4 |
+---------+-----+--------+
5 rows in set (26.19 sec)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+----------+
| index_name | count(*) |
+------------+----------+
| PRIMARY    |     4098 |
| val        |      208 |
+------------+----------+2 rows in set (0.04 sec)
可以看出,此时buffer pool中关于test表有4098个数据页,208个索引页。

select * from test a inner join (select id from test where val=4 limit 300000,5) ;为了防止上次试验的影响,我们需要清空buffer pool,重启mysql。

mysqladmin shutdown
/usr/local/bin/mysqld_safe &

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;

Empty set (0.03 sec)
运行sql:

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id      | val | source | id      |
+---------+-----+--------+---------+
| 3327622 |   4 |      4 | 3327622 |
| 3327632 |   4 |      4 | 3327632 |
| 3327642 |   4 |      4 | 3327642 |
| 3327652 |   4 |      4 | 3327652 |
| 3327662 |   4 |      4 | 3327662 |
+---------+-----+--------+---------+
5 rows in set (0.09 sec)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+----------+
| index_name | count(*) |
+------------+----------+
| PRIMARY    |        5 |
| val        |      390 |
+------------+----------+
2 rows in set (0.03 sec)
我们可以看明显的看出两者的差别:第一个sql加载了4098个数据页到buffer pool,而第二个sql只加载了5个数据页到buffer pool。符合我们的预测。也证实了为什么第一个sql会慢:读取大量的无用数据行(300000),最后却抛弃掉。

而且这会造成一个问题:加载了很多热点不是很高的数据页到buffer pool,会造成buffer pool的污染,占用buffer pool的空间。遇到的问题

为了在每次重启时确保清空buffer pool,我们需要关闭innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup,这两个选项能够控制数据库关闭时dump出buffer pool中的数据和在数据库开启时载入在磁盘上备份buffer pool的数据。
分享到:
评论

相关推荐

    16s rRNA基因的获取(16s rDNA)

    16s rRNA基因,全称为16s ribosomal RNA基因,是细菌、古菌以及线粒体等细胞器中的核糖体RNA的一个亚基,它在细胞的蛋白质合成过程中起到重要作用。16s rDNA则是该基因的DNA序列,包含在细菌的染色体基因组中。由于...

    stc12c5a16s2的手册

    同时,其内部的程序存储器与数据存储器(SRAM)提供了充足的存储空间,满足复杂的程序执行和数据处理需求。 ### I/O口结构与配置 手册深入介绍了STC12C5A16S2的I/O口结构,包括P4/P5口的使用方法,以及I/O口在不同...

    2003554使用QIIME 2分析微生物组16S rRNA基因扩增子测序数据_final1

    《使用QIIME 2分析微生物组16S rRNA基因扩增子测序数据》 微生物组研究在近年来已经成为生命科学研究的热点,其中16S rRNA基因扩增子测序技术因其高通量、成本效益高的特性,被广泛用于微生物群落结构的解析。QIIME...

    使用QIIME 2流程分析微生物组16S rRNA基因扩增子测序数据1

    本文将详细介绍如何使用QIIME 2进行16S rRNA基因扩增子测序数据的分析。 首先,QIIME 2采用了Python 3作为基础语言,确保了与最新的算法和技术兼容,同时支持交互式图表,增强了数据可视化功能。此外,QIIME 2的...

    16S扩增子多样性测序平台、测序数据量选择汇总.pdf

    16S扩增子多样性测序平台、测序数据量选择汇总.pdf16S扩增子多样性测序平台、测序数据量选择汇总.pdf16S扩增子多样性测序平台、测序数据量选择汇总.pdf16S扩增子多样性测序平台、测序数据量选择汇总.pdf16S扩增子...

    reago:从宏基因组学数据回收16S核糖体RNA的组装工具

    从宏基因组学数据回收16S核糖体RNA的组装工具 依存关系: 地狱1.1.1 Readjoiner 1.2 Runbook: 输入:FASTA格式的双端宏基因组读取 输出:从宏基因组读取中回收的16S基因 步骤1:确定16S读取。 命令:python ...

    MY1680U-16S语音芯片使用说明书V1.1.pdf

    技术规格方面,MY1680U-16S支持的音频文件采样率在8到48K之间,比特率则覆盖了8到320Kbps。它具备24位数字模拟转换器(DAC)输出,拥有93dB的动态范围和85dB的信噪比表现。该芯片支持最大16MByte的FLASH存储空间,...

    STC12C5A16S2 AD采集及串口显示

    - 开发过程中,需要编写C语言程序来控制STC12C5A16S2执行AD采集和串口通信任务。 - 程序结构通常包括初始化部分(配置AD转换器和串口)、主循环(定期采集和发送数据)以及中断服务子程序(处理串口发送完成事件)...

    stc12c5a16s_YX5200

    STC12C5A16S_YX5200是基于STC12C5A16S单片机实现的一种MP3播放器方案,主要利用了单片机的UART2串行接口进行通信。这个项目的核心在于将单片机与YX5200 MP3解码芯片进行连接,通过串口2(UART2)进行数据传输,以...

    魅族16s刷写第三方twrp+实测步骤教程

    魅族16s刷写第三方twrp+实测步骤教程 1----请使用本人实测的资源来刷写你的机型 2----资源可以支持当前机型刷写第三方rec 3-----内含刷写资源和详细刷写教程步骤 4-----完美兼容当前不同的版本刷写 5-----资源...

    维基百科:16S_核糖体RNA.pdf

    16S核糖体RNA(16S rRNA)是核糖体中30S亚基的组成成分,是一种存在于所有生物的核糖体中,是细胞蛋白质合成的中心。16S rRNA是核糖体的多个RNA组件之一,在细胞的蛋白质合成过程中起着关键作用,它不仅对核糖体结构...

    MY1690-16S语音芯片使用说明书V1.0(中文)

    MY1690-16S 是深圳市迈优科技有限公司自主研发的一款由串口控制的插卡MP3芯片。支持MP3、WAV格式双解码,模块最大支持32G TF卡,也可外接U盘或USB数据线连接电脑更换SD卡音频文件。

    win16s:在Windows中分析16S rDNA扩增子测序数据的简单管道

    **16S rDNA扩增子测序技术详解** 16S rDNA是细菌和古菌基因组中保守的核糖体RNA(rRNA)序列,由于其在不同物种间的高度保守性和在种内高度多态性,16S rDNA成为微生物分类和群落结构分析的重要分子标记。16S rDNA...

    16S扩增子测序分析流程.pdf

    16S扩增子测序分析流程是一门应用于微生物群落研究的技术,其目的在于分析特定环境中微生物群体的基因组成、功能、多样性和丰度,并进而研究微生物与环境、宿主之间的相互关系。该技术基于宏基因组测序的概念,它与...

    病原微生物检测之16s与宏基因组

    "病原微生物检测之16s与宏基因组" 病原微生物检测是微生物学中的一个重要领域,而16S和宏基因组是其中两个关键技术。本文将对病原微生物检测之16S与宏基因组进行详细的介绍和分析。 微生物基本概念 微生物...

    魅族16s售后9008线刷救砖固件 版本7.3.0.1A

    魅族16s售后9008线刷救砖固件 版本7.3.0.1A,亲自实验100%成功

    16S rRNA克隆文库法分析成人龋病唾液微生物多样性

    在口腔微生物学和龋病研究领域中,16S rRNA克隆文库法是一种广泛应用于微生物种群结构分析的技术。该方法基于16S rRNA基因的序列信息,它是原核生物核糖体中的一种核糖核酸,由于其编码区域高度保守而适合作为微生物...

    PNOZ16/16S 急停继电器、安全门监控器手册.pdf

    5. 机械数据:设备的最大导线截面为300mm²,适用于单芯或多芯导线,拥有插接连接器。尺寸为87x45x121mm,重量为330克。 6. 运行模式:PNOZ16/PNOZ16S支持单通道操作和双通道操作,提供自动复位和手动复位的功能。 ...

    四轴飞控 STC8A8K16S4A12

    - **软件优化**:优化控制算法,降低延迟,提高飞行器的动态响应和稳定性。 通过以上对STC8A8K16S4A12及其在四轴飞控中的应用的详细阐述,我们可以看到,这个微控制器在四轴飞行器的控制系统中扮演了至关重要的...

    数码资源 魅族16s内测降级救砖教程(含线刷工具与线刷固件)

    本资源内容包括:9008线刷工具与驱动、魅族16s Flyme 7.3.0.0A(出厂系统线刷包),以及降级救砖教程。 此降级方法将会清除所有数据,刷机前请做好备份。 刷机前请确保手机能正常开/关机,本教程不可清除锁屏密码,...

Global site tag (gtag.js) - Google Analytics