从一条慢sql优化,深入探讨mysql的优化器优化机制

在某环境发现一个前端请求报错,经查为一条复杂的sql耗时约70s,最终导致前端响应超时。下面叙述下本次问题排查及根因分析过程,供其他同学参考。

本文中使用到的数据库是mariadb,对于mysql也是基本适用。

一,连接算法

使用explain该问题语句,结果如下图所示:
在这里插入图片描述

图一

明显存在一个BNL算法的描述,相对于INLJ算法,BNL算法效率比较低。

下面对mysql相关连接算法做个简单介绍:

**1.**Simple Nested-Loops Join(SNLJ,简单嵌套循环联接)

这是最简单的一种算法,即假设t1,t2两个表关联字段都没有索引,那么查询的过程就是遍历匹配两个表,用文字描述如下:

遍历表t1,对于表中每一行数据row1:

遍历表t2,对于表中每一行数据row2:

判断row1,row2是否满足关联条件:

如果满足,返回关联结果集 <r1,r2>。

上述过程是一个取笛卡尔积的过程,即对于t1中的每一行记录,t2都要全表遍历一次。如果表t1有10000行,t2有20000行,则上述过程一共需要扫码1w+1w*2w条数据,超过1亿条。

其代价之大可想而知,所以mysql并没有直接采用这种算法,而是使用了相对改进的BNL算法。

2.Block Nested-Loops Join(BNL,基于块的嵌套循环联接)

这种算法其实就是将SNLJ中t1表放进内存中(Join Buffer),再从t2表中取到每一行与内存中t1进行匹配。如果join buffer不能一次性放下整个t1表,则会分批次放入。

这种算法使得t2表中的每条记录可以一次性的匹配t1表在内存中的所有记录,如果t1表全都可以放进join buffer,那么t2表只需要做一次全盘扫码。即使t1需要分N次放入join buffer中,t2表也只需要全盘扫码N次,从而减少了扫码t2表的io代价。

此处join buffer size 的配置,就成了影响t2表全盘扫码次数的关键参数,如果该值相对于t1表数据过小,则该算法相对于SNLJ算法优化效果就很有限,从而依然存在性能问题。

Join buffer的大小可以通过show variables like "%join_buffer%"查看,默认为256k。

但是仍然需要对t2表进行多次的全盘扫码,如果t2表特别大,依然会有性能问题。以上是两个表连接字段都不存在索引的情况,如果其中一个表的连接字段存在索引,则优化器会将该索引字段对应的表作为被驱动表,进而使用相对高效的INLJ算法。

3.Index Nested-Loops Join(INLJ,基于索引的****嵌套循环联接)

相对于SNLJ算法,INLJ算法的改进点在于被驱动表(内表t2)查找时使用到了索引,这样就完全避免了t2表的多次全盘扫码的代价,进而大大提高了执行效率。

备注:

对于BNL算法加入Hash算法优化,从而形成BNLH算法;以及为了减少INLJ算法中的连接字段为非主键索引时带来的随机IO问题,从而改进的BKA算法;在BKA算法中使用hash算法进而形成BKAH算法,与本文关系不大,不在此处探讨。

连接算法小结

通过上述分析,我们可以得到以下结论:

1.在两表关联时,被驱动表关联字段尽量带有索引,进而使用INLJ算法。

2.被驱动表的关联索引最好是主键索引,这样可以减少关联时的回表操作。

3.无论是INLJ算法还是BNL算法,小表驱动大表都是都可以减少io次数。

4,左连接驱动表是左表,右连接驱动表是右表,内连接驱动表由优化器自主选择。

案例分析

回过头来,看图一,发现BNL算法记录所对应的table属性为,说明这是一个派生表,而派生表是不存在索引的,所以作为被驱动表时使用了BNL算法。

怎么解决呢?

按照上面的思路,就是将这个不存在索引的派生表作为驱动表,而非被驱动表,这样就可以使用到被驱动表关联字段的索引,进而使用INLJ算法。

解决方案

对于这条内连接语句,需要用STRAIGHT_JOIN 指定驱动表即可。即由t1 JOIN t2改为t1 STRAIGHT_JOIN t2,即指定了t1作为驱动表,而不是由优化器自主选择。

通过STRAIGHT_JOIN指定内连接的驱动表,在问题现场,成功将该问题语句的耗时从70s降低到0.02s。

然而故事并没有结束

还没有完

上述做法确实解决了现场的问题,但是留下了一个疑问:为什么优化器没有选择关联字段带有索引的表作为被驱动表,而选择了一个没有索引的派生表作为被驱动表呢?

也就是优化器执行的逻辑是什么呢?

基于代价的索引选择算法

事实上,优化器是通过一种基于代价的计算方式来选择索引,以及两表之间的关联顺序的。

代价模型包括CPU代价和IO代价,不同操作的代价如下:

*操作**说明**代价值*
disk_temptable_create_cost创建磁盘临时表代价40
disk_temptable_row_cost磁盘临时表每行代价1
memory_temptable_create_cost创建内存临时表代价2
memory_temptable_row_cost内存临时表每行代价0.2
key_compare_cost比较关键字代价0.1
row_evaluate_cost行访问代价0.2
io_block_read_cost读取磁盘块代价1
memory_block_read_cost读取内存块代价1

观察选择过程

我们可以通过information_schema.optimizer_trace中的信息观察优化器执行的具体流程。

set optimizer_trace=‘enabled=on’;

select * from … 执行所需分析的的操作。

select * from information_schema.optimizer_trace limit 1;

set optimizer_trace=‘enabled=off’;

可以看到该条语句具体的执行流程,及各种选择方案的代价计算值。

如:

{

​ “rows_estimation”: [

​ {

​ “table”: “a”,

​ “table_scan”: {

​ “rows”: 18325,

​ “cost”: 467

​ }

​ },

​ {

​ “table”: “b”,

​ “table_scan”: {

​ “rows”: 16241,

​ “cost”: 402

​ }

​ }

​ ]

​ },

​ {

​ “considered_execution_plans”: [

​ {

​ “plan_prefix”: [],

​ “table”: “b”,

​ “best_access_path”: {

​ “considered_access_paths”: [

​ {

​ “access_type”: “scan”,

​ “resulting_rows”: 16241,

​ “cost”: 402,

​ “chosen”: true

​ }

​ ],

​ “chosen_access_method”: {

​ “type”: “scan”,

​ “records”: 16241,

​ “cost”: 402,

​ “uses_join_buffering”: false

​ }

​ },

​ “rows_for_plan”: 16241,

​ “cost_for_plan”: 3650.2

通过上面的内容,可以看到各种连接方式的花费。

当然,优化器并没有穷尽所有可能的组合,因为这样可能使评估动作变得太过耗时,所以优化器只会选择一部分可能的组合进行尝试,尤其实在关联表的数量很多的时候。

关于优化器遍历深度,可由optimizer_prune_level, optimizer_search_depth等参数进行控制。

所以在连接表的数量特别多的时候,更有可能无法得到最佳的执行策略。

行数从何而来

除了关联表特别的多的问题外,还有个问题是造成评估错误的常见原因:评估的数据来源不准确。

上面需要评估的数据行数(rows_estimation.table_scan.rows)是从哪来的呢?

答案是从information_schema.INNODB_SYS_TABLESTATS表里(mysql.innodb_table_stats,mysql.innodb_index_stats)也有相近且更详细的信息。

经过对比实际数据发现,这个数据跟实际数据并不一致。

那么这个数据是从哪来的呢,又是为什么不一致呢?

统计字段的采样机制

Innodb引擎对每张表并没有存一个字段来精确统计每张表的数据行数,这应该是基于性能上的考虑。否则就要对增删操作时,同时更新这个统计字段,且在并发操作时对该字段进行加锁,这将导致严重的并发度下降的问题。

*Innodb统计字段的方案*

既然不维护这样一个物理字段,Innodb引擎取而代之的是使用采样的方式,来得到一个近似索引数据行数(对于主键索引来说就是表行数)。采样方式是根据采样页数(参数名为innodb_stats_persistent_sample_pages ,默认值是20页。增加该参数可以使采样更准确,但会消耗更多资源),统计这些页数的数据量,再根据总页数,估算出一个行数的近似值。

上文中的优化器使用到的行数,就是这样一个采样得出的近似值。

这个采样行数什么时候会更新呢?每次有增删数据都需要重新采样吗?

Innodb采样统计自动触发机制。

在这里插入图片描述

上图为mariadb源码,从源码中可以看到,在开启统计持久化(参数为innodb_stats_persistent,默认开启)且开启自动统计(参数为innodb_stats_auto_recalc,默认开启)时,相关数据变动超过10%,才会触发重新采样。

既然mariadb有自动采样机制,为什么统计数据还会不准呢?

自动采样机制的弊端

*弊端一*

第一个问题很明显,那就是前文提到的变动数据要大于10%才会触发重新采样,那么对于大表来说,这个触发阈值将会是数十万甚至上百万行的变动,导致大表在长时间段内不能得到重新采样统计,进而该统计数据将会长时间与实际值产生较大差异,进而可能导致优化器的数据来源不准确,做出不好的优化策略。

*弊端二*

除此之外还有个隐含的问题,就是information_schema.INNODB_SYS_TABLESTATS中的MODIFIED_COUNTER是保存在内存中的,当mariadb重启后,该数据就归零了,进而导致需要MODIFIED_COUNTER重新计数。这将延后触发10%变动阈值的时间,对大表的影响尤为严重。

解决自动采样统计弊端的方案

有什么方法可以解决上述问题?

*我们可以主动触发该采样统计。*

即使用:analyze table 表名称,来手动触发对该表的采样统计。

也可以使用mariadb自带的脚本工具mysqlcheck 对整个库进行优化。

如在mysql/bin目录下执行:

./mysqlcheck --user=root --password --host=localhost --port=3306 --analyze --databases 数据库名

运维建议

对于大表,尤其是数据重启后,可根据需要,一定时间自动执行一下上述更新索引语句。

此操作一般都很快,不过仍建议在业务低峰时期作此操作,因为这个操作会导致表上加读锁。

总结

本文通过一次现场sql慢查询的问题,层层分析,挖掘其发生的根本原因。同时分享了mysql连接查询时的优化机制,包括基于代价的查询优化机制,优化器优化过程,统计字段的采样机制,自动采样的触发机制,以及统计字段自动采样机制存在的问题,并给出了解决方案。希望对遇到相关问题的同学有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934004.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+OSS文件(图片))上传

SpringBoot整合OSS实现文件上传 以前,文件上传到本地(服务器,磁盘),文件多,大,会影响服务器性能 如何解决? 使用文件服务器单独存储这些文件,例如商业版–>七牛云存储,阿里云OSS,腾讯云cos等等 也可以自己搭建文件服务器(FastDFS,minio) 0 过程中需要实名认证 … 1 开…

Linux其三,yum源配置,定时任务,免密登录和查找命令

目录 一、Linux的两种软件安装方式 1、Yum源配置 2、linux中软件安装的另一种方式 rpm 3、安装mysql8.0 二、对虚拟机进行克隆 1、先关机 2、最新的状态&#xff0c;整个快照 3、开始克隆 4、修改克隆的服务器的硬件设置 5、修改克隆机的IP地址&#xff08;因为跟第一…

linux环境GitLab服务部署安装及使用

一、GitLab介绍 GitLab是利用Ruby onRails一个开源的版本管理系统&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 二、GitLab安装 1、先安装相关依赖 yum -y install policycoreutils openssh-server openssh-clients postf…

/usr/local/go/bin/go: cannot execute binary file: Exec format error

现象&#xff1a;ubuntu中安装go软件环境&#xff0c;报上述错误 原因&#xff1a;系统与软件不适配 解决&#xff1a;查看本系统的版本 找到x86-64对应的go版本即可

技术 + 舞蹈,探秘 SpringBoot 硬核广场舞团

3 系统分析 3.1 系统可行性分析 3.1.1 经济可行性 由于本系统是作为毕业设计系统&#xff0c;且系统本身存在一些技术层面的缺陷&#xff0c;并不能直接用于商业用途&#xff0c;只想要通过该系统的开发提高自身学术水平&#xff0c;不需要特定服务器等额外花费。所有创造及工作…

MySQL Workbench基本使用

MySQL Workbench 是一款由 MySQL官方开发和提供的统一可视化工具&#xff0c;专为数据库管理员、开发者和数据架构师设计。它提供了数据建模、SQL 开发和数据库管理的全面功能&#xff0c;支持 Windows、Linux 和 macOS 操作系统。 MySQL Workbench 是一个强大的工具&#xff…

fedora下Jetbrains系列IDE窗口中文乱码解决方法

可以看到窗口右部分的中文内容为小方块。 进入 Settings - Appearance & Behavior - Appearance - Use custom font : Note Sans Mono CJK SC &#xff0c;设置后如下图&#xff1a;

厦门凯酷全科技有限公司抖音电商服务的卓越典范

在短视频和直播带货迅速崛起的时代&#xff0c;厦门凯酷全科技有限公司&#xff08;以下简称“凯酷全科技”&#xff09;以其专业的服务、创新的精神以及对市场的深刻理解&#xff0c;在抖音电商领域中脱颖而出&#xff0c;成为众多品牌商家信赖的选择。本文将深入探讨凯酷全科…

Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)

Android显示系统&#xff08;02&#xff09;- OpenGL ES - 概述 Android显示系统&#xff08;03&#xff09;- OpenGL ES - GLSurfaceView的使用 Android显示系统&#xff08;04&#xff09;- OpenGL ES - Shader绘制三角形 Android显示系统&#xff08;05&#xff09;- OpenGL…

Ubuntu中配置交叉编译工具的三条命令的详细研究

关于该把下面的三条交叉编译配置语句加到哪里&#xff0c;详情见 https://blog.csdn.net/wenhao_ir/article/details/144326545 的第2点。 现在试解释下面三条交叉编译配置语句&#xff1a; export ARCHarm export CROSS_COMPILEarm-buildroot-linux-gnueabihf- export PATH$…

【环境搭建】WordPress本地部署搭建及历史版本插件安装(windows系统)

&#x1f3d8;️个人主页&#xff1a; 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 【环境搭建】WordPress本地部署搭建及历史版本插件安装&#xff08;windows系统&#xff09; WordPress搭建环境部署&#xff08;…

【开源免费】基于SpringBoot+Vue.JS大创管理系统(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 081 &#xff0c;文末自助获取源码 \color{red}{T081&#xff0c;文末自助获取源码} T081&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

47 基于单片机的书库环境监测

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11湿度传感器检测湿度&#xff0c;DS18B20温度传感器检测温度&#xff0c; 采用滑动变阻器连接数模转换器模拟二氧化碳和氧气浓度检测&#xff0c;各项数值通过lc…

outlook软件配置邮箱提示“到邮件服务器的加密连接不可用”

outlook软件配置邮箱提示“到邮件服务器的加密连接不可用” 问题描述&#xff1a; outlook软件里邮箱提示“已断开”或配置邮箱时提示“到邮件服务器的加密连接不可用”。 解决方案&#xff1a; 一、更改注册表&#xff08;可先导出备份&#xff09; winr&#xff0c;输入re…

Jmeter进阶篇(30)深入探索 JMeter 监听器

前言 在性能测试领域里,Apache JMeter 是一款经典而强大的工具,而其中的监听器(Listeners)组件更是发挥着不可或缺的关键作用。 监听器就像敏锐的观察者,默默记录测试执行过程中的各种数据,作为系统性能分析的数据依据。 本文将带你全方位走进 JMeter 监听器的奇妙世界,…

微调 Llama 3.2:让 AI 更好地读取医学图像

您是否想知道人工智能模型如何学习理解医学图像&#xff1f;今天&#xff0c;我将带您完成一个令人兴奋的项目&#xff1a;微调 Meta 的 Llama 3.2 Vision 模型来分析放射线图像。如果您不是技术专家&#xff0c;也不要担心 - 我会用简单的术语来解释。 它是如何运作的&#x…

(长期更新)《零基础入门 ArcGIS(ArcMap) 》实验二----网络分析(超超超详细!!!)

相信实验一大家已经完成了&#xff0c;对Arcgis已进一步熟悉了&#xff0c;现在开启第二个实验 ArcMap实验--网络分析 目录 ArcMap实验--网络分析 1.1 网络分析介绍 1.2 实验内容及目的 1.2.1 实验内容 1.2.2 实验目的 2.2 实验方案 2.3 实验流程 2.3.1 实验准备 2.3.2 空间校正…

【Spring项目】表白墙,留言板项目的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;项目实现准备 1&#xff1a;需求 2&#xff1a;准备工作 &#xff08;1&#xff09;…

如何在 Ubuntu 上安装开源监控工具 Uptime Kuma

简介 Uptime Kuma&#xff08;或简称 Kuma&#xff09;是一个开源监控工具&#xff0c;用于监控 HTTP、HTTPS、DNS 等协议的服务。Uptime Kuma 提供多种功能&#xff0c;如多语言支持、多个状态页面、代理支持等。 接下来&#xff0c;我将一步一步教大家如何进行安装和部署&am…

Idea实现定时任务

定时任务 什么是定时任务&#xff1f; 可以自动在项目中根据设定的时长定期执行对应的操作 实现方式 Spring 3.0 版本之后自带定时任务&#xff0c;提供了EnableScheduling注解和Scheduled注解来实现定时任务功能。 使用SpringBoot创建定时任务非常简单&#xff0c;目前主要…