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

2024-08-29 09:03:39 浏览数 (2)

前言

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

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

LIMIT 语句

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

代码语言:javascript复制
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语句,将上一页的最大值作为查询条件。

代码语言:javascript复制
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语句中字段类型与查询变量类型不匹配是另一个常见错误。

代码语言:javascript复制
EXPLAIN EXTENDED SELECT
	*
FROM
	my_balance b
WHERE
	b.bpn = 14000000123
AND b.isverified IS NULL;

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

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

代码语言:javascript复制
SELECT
	*
FROM
	my_balance b
WHERE
	b.bpn = '14000000123'
AND b.isverified IS NULL;

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


关联更新、删除

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

代码语言:javascript复制
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,可以显著提高更新语句的执行速度:

代码语言:javascript复制
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通常无法利用索引进行混合排序,但在某些场景下可以通过特殊的重写方式提高性能。

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

代码语言:javascript复制
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;

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

代码语言:javascript复制
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子句时,仍然采用嵌套子查询的执行方式,这会导致性能问题。

代码语言:javascript复制
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来提高查询效率:

代码语言:javascript复制
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语句时,程序员需要将算法的思想或意识融入其中。

0 人点赞