当前位置: 首页 > article >正文

MySQL · 性能优化 · 提高查询效率的实用指南(上)

前言

在过去的几年里,MySQL作为一款开源数据库,因其稳定性和性能得到了广泛的应用。始终保持着强劲的增长趋势,越来越多的企业和开发者将其作为首选数据库,甚至有部分企业从Oracle迁移至MySQL。然而,随着使用的普及,MySQL在实际应用中也暴露出了一些常见问题,尤其是当SQL语句不够优化时,可能会导致响应时间慢、CPU使用率高等性能瓶颈问题。

请在此添加图片描述

今天我总结了常见的SQL错误用法,供大家参考:

LIMIT 语句

错误用法: 在应用程序中,分页查询是非常常见的操作场景。然而,LIMIT语句在数据量较大的情况下容易出现性能问题。

SELECT
	*
FROM
	operation
WHERE
	type = 'SQLStats'
AND NAME = 'SlowLog'
ORDER BY
	create_time
LIMIT 1000, 10;

LIMIT参数较小时,例如LIMIT 1000, 10,查询性能尚可,但当参数变为LIMIT 1000000,10时,即使程序员仅需提取10条记录,查询时间依然可能显著增加。原因在于数据库必须从头开始扫描并计算每一行,直到达到所需的记录位置。

优化方案: 为了避免这种性能问题,可以通过重新设计SQL语句,将上一页的最大值作为查询条件。

SELECT
	*
FROM
	operation
WHERE
	type = 'SQLStats'
AND NAME = 'SlowLog'
AND create_time > '2017-03-16 14:00:00'
ORDER BY
	create_time
LIMIT 10;

这种方式确保了查询时间基本固定,不会随着数据量的增长而变化。

原理解析: MySQL在执行LIMIT查询时,必须遍历满足条件的所有记录,直到到达指定的偏移量。因此,随着LIMIT偏移量的增加,查询的时间复杂度线性增加。通过将上一页的最大值作为查询条件,可以避免数据库扫描大量无关记录,从而大幅提高查询效率。


隐式转换

错误用法: SQL语句中字段类型与查询变量类型不匹配是另一个常见错误。

EXPLAIN EXTENDED SELECT
	*
FROM
	my_balance b
WHERE
	b.bpn = 14000000123
AND b.isverified IS NULL;

在上述语句中,bpn字段定义为varchar(20),而查询条件中的14000000123是一个整数。MySQL会自动将字符串转换为数字再进行比较,这会导致索引失效,进而影响查询性能。

优化方案: 为避免隐式转换,应确保查询变量与字段类型一致。例如,可以将查询条件中的整数转换为字符串形式:

SELECT
	*
FROM
	my_balance b
WHERE
	b.bpn = '14000000123'
AND b.isverified IS NULL;

原理解析: 当MySQL遇到类型不匹配的情况时,会尝试进行隐式转换,以确保语句能够执行。然而,这种转换通常会导致索引失效,因为索引的原始数据类型与转换后的数据类型不匹配。因此,查询效率会大幅下降。通过确保查询条件与字段类型一致,可以避免不必要的转换操作,从而提升查询效率。


关联更新、删除

错误用法: 虽然MySQL 5.6引入了物化特性来优化查询性能,但对于更新或删除操作,仍需手工重写为JOIN,以提高执行效率。例如,以下UPDATE语句的执行方式为循环嵌套子查询(DEPENDENT SUBQUERY):

UPDATE operation o
SET STATUS = 'applying'
WHERE
	o.id IN (
		SELECT
			id
		FROM
			(
				SELECT
					o.id,
					o. STATUS
				FROM
					operation o
				WHERE
					o. GROUP = 123
				AND o. STATUS NOT IN ('done')
				ORDER BY
					o.parent,
					o.id
				LIMIT 1
			) t
	);

优化方案: 通过将子查询改写为JOIN,可以显著提高更新语句的执行速度:

UPDATE operation o
JOIN (
	SELECT
		o.id,
		o. STATUS
	FROM
		operation o
	WHERE
		o. GROUP = 123
	AND o. STATUS NOT IN ('done')
	ORDER BY
		o.parent,
		o.id
	LIMIT 1
) t ON o.id = t.id
SET STATUS = 'applying';

原理解析: MySQL在处理嵌套子查询时,需要为每一行执行子查询,这通常会导致性能问题。而JOIN操作通过在一张表中查找匹配的行,避免了多次子查询的执行,从而显著提升性能。


混合排序

错误用法: MySQL通常无法利用索引进行混合排序,但在某些场景下可以通过特殊的重写方式提高性能。

以下查询语句执行计划显示为全表扫描:

SELECT
	*
FROM
	my_order o
INNER JOIN my_appraise a ON a.orderid = o.id
ORDER BY
	a.is_reply ASC,
	a.appraise_time DESC
LIMIT 0, 20;

优化方案: 可以将查询拆分为两个部分并合并结果,从而提高查询效率:

SELECT
	*
FROM
	(
		(
			SELECT
				*
			FROM
				my_order o
			INNER JOIN my_appraise a ON a.orderid = o.id
			WHERE
				is_reply = 0
			ORDER BY
				appraise_time DESC
			LIMIT 0,
			20
		)
		UNION ALL
			(
				SELECT
					*
				FROM
					my_order o
				INNER JOIN my_appraise a ON a.orderid = o.id
				WHERE
					is_reply = 1
				ORDER BY
					appraise_time DESC
				LIMIT 0,
				20
			)
	) t
ORDER BY
	is_reply ASC,
	appraise_time DESC
LIMIT 20;

原理解析: MySQL在混合排序的情况下,通常需要对整个数据集进行排序,这会导致较高的计算成本。通过将查询拆分为多个部分,再合并结果,可以减少MySQL在排序过程中的负担,进而提高查询速度。


EXISTS语句

错误用法: MySQL在处理EXISTS子句时,仍然采用嵌套子查询的执行方式,这会导致性能问题。

SELECT
	*
FROM
	my_neighbor n
LEFT JOIN my_neighbor_apply sra ON n.id = sra.neighbor_id
AND sra.user_id = 'xxx'
WHERE
	n.topic_status < 4
AND EXISTS (
	SELECT
		1
	FROM
		message_info m
	WHERE
		n.id = m.neighbor_id
	AND m.inuser = 'xxx'
)
AND n.topic_type <> 5;

优化方案: 可以通过将EXISTS子查询改写为JOIN来提高查询效率:

SELECT
	*
FROM
	my_neighbor n
INNER JOIN message_info m ON n.id = m.neighbor_id
AND m.inuser = 'xxx'
LEFT JOIN my_neighbor_apply sra ON n.id = sra.neighbor_id
AND sra.user_id = 'xxx'
WHERE
	n.topic_status < 4
AND n.topic_type <> 5;

原理解析: MySQL在执行EXISTS子查询时,会为主查询的每一行执行一次子查询,导致性能问题。通过将EXISTS改写为JOIN,可以避免不必要的重复查询,从而显著提高执行效率。

总结

数据库编译器负责生成执行计划,这一计划将决定SQL语句的实际执行方式。然而,编译器仅仅是尽力而为,因为所有数据库的编译器都并非完美无缺。许多性能问题在其他数据库系统中也同样存在。唯有深入了解数据库编译器的特性,我们才能规避其不足之处,从而编写出高性能的SQL语句。

因此,在设计数据模型和编写SQL语句时,程序员需要将算法的思想或意识融入其中。


http://www.kler.cn/a/294334.html

相关文章:

  • 【数学二】线性代数-线性方程组-齐次线性方程组、非齐次线性方程组
  • vue请求数据报错,设置支持跨域请求,以及2种请求方法axios或者async与await
  • 十三、注解配置SpringMVC
  • 使用kalibr_calibration标定相机(realsense)和imu(h7min)
  • vue项目npm run serve出现【- Network: unavailable】(从排查到放弃)
  • uniapp打包华为,提示请提供64位版本软件包后再提交审核
  • 第十四节:学习Springboot 的restful接口风格(自学Spring boot 3.x的第三天)
  • ROADM(可)-介绍
  • 【Linux网络编程】协议|OSI模型|TCP/IP模型|局域网通信|跨网络通信|地址管理|流程图
  • Kafka和ES加密
  • 深度学习dataset等概念
  • 【杂谈】-国产单片机学习成本高的原因分析
  • Deep Ocr
  • Flask如何处理静态文件
  • Spring拦截器与Servlet过滤器区别
  • Unity3D DOTS Component详解
  • 人机交互中的当斯定律
  • Redis数据持久化方法
  • 要在nginx中配置后端三个服务器的轮询和加权
  • Linux下安装和使用SVN及常用操作命令详解
  • 玩转Python Turtle库,实现满屏飘字的魔法!
  • vite项目配置本地开发使用https访问
  • 后背筋膜炎怎么治疗才能除根
  • 【数据结构】你知道什么是二叉树的顺序存储结构吗?
  • 移动端视频编辑SDK解决方案,AI语音识别添加字幕
  • OpenAI计划提高ChatGPT的价格——你需要了解的事项