一、背景

我们的一个业务的数据是存放在数据库百库十表里的,其中存在一个定时任务对其监听,将一批"未处理"的数据处理后标记为"已处理"。今天开发反馈查询捞数据报超时错误。

二、分析

首先检查sql语法是否有问题,检查后是仅仅是一个普通的sql语句,最大拉取数据为10w条。

其次检查sql中where条件是否使用了主键或者索引,检查后发现这里并没有使用索引,所以这里需要优化。

检查线上该表定义,检查添加索引情况,主要使用这两种办法对已存在表进行检查:

  1. 使用 SHOW CREATE TABLE 命令,该命令可以显示表的创建语句,其中包含了字段的索引信息。例如:
1
SHOW CREATE TABLE table_name;
  1. 使用 DESC 命令或 SHOW INDEX 命令,这两个命令可以列出表的字段信息和索引信息。例如:
1
2
DESC table_name;
SHOW INDEX FROM table_name;

以上两个命令中,如果某个字段有唯一索引,则在 “Key” 列中显示 “UNI”,如果是普通索引,则显示 “MUL”(如果该字段上还有其他索引,则会显示为 “MUL”)。如果该字段没有任何索引,则 “Key” 列显示为 “NULL”。

检查后发现查询字段没有使用索引,所以这里需要添加索引。

三、索引优化

3.1 添加索引需要考虑哪些点?

  1. 查询频率:添加索引的目的是为了加快查询速度,因此需要考虑查询该字段的频率和查询速度的优化效果。如果查询频率较高,可以考虑添加索引来优化查询速度。
  2. 数据量:数据量越大,添加索引的代价越高,因为索引需要占用磁盘空间、增加写入操作的时间和 CPU 开销。因此需要权衡索引的代价和查询速度的优化效果。
  3. 表结构和查询语句:添加索引需要根据表的结构和查询语句来选择索引类型和索引字段。例如,如果表中有多个字段经常一起查询,可以考虑添加联合索引来优化查询速度。
  4. 更新操作:添加索引会增加写入操作的时间和 CPU 开销,因此需要考虑更新操作的频率和更新代价。如果表中的数据频繁更新,可以考虑减少索引的数量和范围,以减少更新代价。

3.2 需要考虑这个索引的区分度吗

是的,索引的区分度是添加索引时需要考虑的重要因素之一。索引的区分度指的是索引字段中不同值的数量与总记录数之比,区分度越高,索引的效果越好。 如果索引字段的区分度较低,即存在大量重复值,那么添加索引的效果就会降低。因为对于重复值,查询时需要访问多个相同的索引项,而这些索引项的指针又指向同一个数据块,这样就会浪费磁盘空间和 CPU 时间。 在选择索引字段时,应该优先选择区分度高的字段作为索引字段。如果没有高区分度的字段可用,可以考虑使用多个字段的组合作为联合索引来提高索引的区分度。

因此,这里先使用区分度高的字段作为索引字段,线上观察下,之后可以考虑使用联合索引来提高区分度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
CREATE TABLE `table_test` (
  `FId` bigint(20) NOT NULL AUTO_INCREMENT,
  `FName` varchar(128) DEFAULT NULL,
  `FStatus` int(10) UNSIGNED DEFAULT NULL,
  `FPreviewCondition` int(10) UNSIGNED DEFAULT NULL,
  `FLastModTime` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`FId`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test1', 1, 0, 1633737600);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test2', 2, 1, 1633737660);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test3', 3, 2, 1633737720);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test4', 4, 0, 1633737780);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test5', 5, 1, 1633737840);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test6', 6, 2, 1633737900);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test7', 7, 0, 1633737960);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test8', 8, 1, 1633738020);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test9', 9, 2, 1633738080);
INSERT INTO `table_test` (`FName`, `FStatus`, `FPreviewCondition`, `FLastModTime`) VALUES ('test10', 10, 0, 1633738140);


mysql> explain select FId,FName from table_test WHERE FStatus = 1 AND FPreviewCondition = 1 AND FLastModTime >= 1695794400 LIMIT 0,10;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | table_test | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

查看并添加索引
mysql> SHOW INDEX FROM table_test;
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table      | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| table_test |          0 | PRIMARY  |            1 | FId         | A         |          10 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.01 sec)

CREATE INDEX idx_FLastModTime ON table_test (FLastModTime);

mysql> SHOW INDEX FROM table_test;
+------------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table      | Non_unique | Key_name         | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| table_test |          0 | PRIMARY          |            1 | FId          | A         |          10 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| table_test |          1 | idx_FLastModTime |            1 | FLastModTime | A         |          10 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+------------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.00 sec)

重新检查索引使用条件
mysql> explain select FId,FName from table_test WHERE FStatus = 1 AND FPreviewCondition = 1 AND FLastModTime >= 1695794400 LIMIT 0,10;
+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+------------------------------------+
| id | select_type | table      | partitions | type  | possible_keys    | key              | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | table_test | NULL       | range | idx_FLastModTime | idx_FLastModTime | 9       | NULL |    1 |    10.00 | Using index condition; Using where |
+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)

结论:添加索引或者联合索引改善查询耗时。

四、其他优化

在添加了联合索引后,部分数据库表仍然有超时,则看数据是不是有倾斜?

1
select count(*) cnt, column1 from table group by column1 order by cnt limit 100;

数据倾斜导致SQL执行慢,异步任务遍历查询SQL执行超时。在不同的生产环境和项目背景下,可能会遇到其他更加棘手的问题(比如各种数据倾斜) 解决办法:先数据归档清除这部分脏数据,之后调整业务逻辑进行优化。

参考文档:https://shuyi.tech/archives/quick-start-of-mysql-explain