Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Latest commit

 

History

History
History
477 lines (320 loc) · 27.3 KB

File metadata and controls

477 lines (320 loc) · 27.3 KB
Copy raw file
Download raw file
Outline
Edit and raw actions
title 常见SQL优化手段总结
description 本文系统总结常见的 SQL 优化手段,涵盖慢 SQL 定位与分析(EXPLAIN、Show Profile)、索引优化策略、查询重写技巧、分页优化等实战方法,帮助你快速提升数据库查询性能。
category 高性能
head
meta
name content
keywords
SQL优化,慢SQL,EXPLAIN执行计划,索引优化,MySQL优化,查询优化,分页优化,Show Profile

避免使用 SELECT *

  • SELECT * 会消耗更多的 CPU。
  • SELECT * 无用字段增加网络带宽资源消耗,增加数据传输时间,尤其是大字段(如 varchar、blob、text)。
  • SELECT * 无法使用 MySQL 优化器覆盖索引的优化(基于 MySQL 优化器的“覆盖索引”策略又是速度极快,效率极高,业界极为推荐的查询优化方式)
  • SELECT <字段列表> 可减少表结构变更带来的影响。

尽量避免多表做 join

阿里巴巴《Java 开发手册》中有这样一段描述:

【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联 的字段需要有索引。

尽量避免多表做 join

join 的效率比较低,主要原因是因为其使用嵌套循环(Nested Loop)来实现关联查询,以前常见的实现效率都不是很高:

  • Simple Nested-Loop Join :直接使用笛卡尔积实现 join,逐行遍历/全表扫描,效率最低。
  • Block Nested-Loop Join (BNL) :利用 JOIN BUFFER 进行优化。注意:在 MySQL 8.0.20 及更高版本中,BNL 已被 Hash Join 取代,Hash Join 通常能将非索引列关联的复杂度从 O(M*N) 降低到接近 O(M+N)。
  • Index Nested-Loop Join :在必要的字段上增加索引,性能得到进一步提升。

实际业务场景避免多表 join 常见的做法有两种:

  1. 单表查询后在内存中自己做关联 :对数据库做单表查询,再根据查询结果进行二次查询,以此类推,最后再进行关联。
  2. 数据冗余,把一些重要的数据在表中做冗余,尽可能地避免关联查询。很笨的一种做法,表结构比较稳定的情况下才会考虑这种做法。进行冗余设计之前,思考一下自己的表结构设计的是否有问题。

更加推荐第一种,这种在实际项目中的使用率比较高,除了性能不错之外,还有如下优势:

  1. 拆分后的单表查询代码可复用性更高 :join 联表 SQL 基本不太可能被复用。
  2. 单表查询更利于后续的维护 :不论是后续修改表结构还是进行分库分表,单表查询维护起来都更容易。

不过,如果系统要求的并发量不大的话,我觉得多表 join 也是没问题的。很多公司内部复杂的系统,要求的并发量不高,很多数据必须 join 5 张以上的表才能查出来。

深度分页优化

深度分页问题的根本原因在于:当 LIMIT 的偏移量过大时,MySQL 需要扫描并跳过大量记录才能获取目标数据,查询优化器可能放弃索引而选择全表扫描。此时即使有索引,也无法避免大量的回表操作,导致查询性能急剧下降。

本文介绍了四种常见的深度分页优化方案,各方案的特点及适用场景对比如下:

优化方案 核心思路 适用场景 限制
范围查询 记录上一页最后一条 ID,通过 WHERE id > last_id LIMIT n 获取下一页 ID 连续、按 ID 排序、允许游标式翻页 不支持跳页、ID 不连续时失效、非 ID 排序不适用
子查询 先通过子查询获取起始主键,再根据主键过滤 需要支持传统 OFFSET 翻页 子查询可能产生临时表、仅适用于 ID 正序
延迟关联 INNER JOIN 将分页转移到主键索引,减少回表 大数据量分页、需要传统翻页逻辑 SQL 相对复杂
覆盖索引 建立包含查询字段的联合索引,避免回表 查询字段固定、可建立合适索引 字段较多时索引维护成本高、大结果集可能走全表扫描

方案选择建议

  • 优先使用延迟关联:对于大多数需要支持传统 LIMIT offset, size 翻页逻辑的场景,延迟关联是性能和可维护性较好的选择。
  • 考虑范围查询(游标分页):如果业务允许使用"下一页"式的游标翻页(如社交媒体 feed 流、无限滚动),范围查询性能最佳且稳定。
  • 覆盖索引作为补充:当查询字段固定且数量不多时,可配合其他方案建立覆盖索引进一步优化。

注意事项

  • 无论采用哪种方案,都应注意监控实际执行计划(EXPLAIN),确保优化器按预期使用索引。
  • 对于超深分页(如百万级偏移量),应从业务层面评估是否真的需要支持,考虑限制最大翻页数或采用其他检索方式(如搜索引擎)。

详细介绍可以阅读这篇文章:深度分页介绍及优化建议

建议不要使用外键与级联

阿里巴巴《Java 开发手册》中有这样一段描述:

不得使用外键与级联,一切外键概念必须在应用层解决。

网络上已经有非常多分析外键与级联缺陷的文章了,个人认为不建议使用外键主要是因为对分库分表不友好,性能方面的影响其实是比较小的。

选择合适的字段类型

存储字节越小,占用也就空间越小,性能也越好。

a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整型数据。

数字是连续的,性能更好,占用空间也更小。

MySQL 提供了两个方法来处理 ip 地址

  • INET_ATON() : 把 IPv4 转为无符号整型(4 字节,32 位)。对于 IPv6,可使用 INET6_ATON() 转为 16 字节(128 位)的二进制字符串。
  • INET_NTOA() :把整型的 ip 转为地址

插入数据前,先用 INET_ATON() 把 ip 地址转为整型,显示数据时,使用 INET_NTOA() 把整型的 ip 地址转为地址显示即可。

b.对于非负型的数据 (如自增 ID,整型 IP,年龄) 来说,要优先使用无符号整型来存储。

无符号相对于有符号可以多出一倍的存储空间

SIGNED INT -2147483648~2147483647
UNSIGNED INT 0~4294967295

c.小数值类型(比如年龄、状态表示如 0/1)优先使用 TINYINT 类型。

d.对于日期类型来说, 一定不要用字符串存储日期。可以考虑 DATETIME、TIMESTAMP 和 数值型时间戳。

这三种种方式都有各自的优势,根据实际场景选择最合适的才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:

类型 存储空间 日期格式 日期范围 是否带时区信息
DATETIME 5~8 字节 YYYY-MM-DD hh:mm:ss[.fraction] 1000-01-01 00:00:00[.000000] ~ 9999-12-31 23:59:59[.999999]
TIMESTAMP 4~7 字节 YYYY-MM-DD hh:mm:ss[.fraction] 1970-01-01 00:00:01[.000000] ~ 2038-01-19 03:14:07[.999999]
数值型时间戳 4 字节 全数字如 1578707612 1970-01-01 00:00:01 之后的时间

MySQL 时间类型选择的详细介绍请看这篇:MySQL 时间类型数据存储建议

e.金额字段用 decimal,避免精度丢失。

decimal 用于存储有精度要求的小数比如与金钱相关的数据,可以避免浮点数带来的精度损失。

在 Java 中,MySQL 的 decimal 类型对应的是 Java 类 java.math.BigDecimal

BigDecimal的详细介绍请参考这篇:BigDecimal 详解

f.尽量使用自增 id 作为主键。

如果主键为自增 id 的话,每次都会将数据加在 B+树尾部(本质是双向链表),时间复杂度为 O(1)。在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了。

如果主键是非自增 id 的话,为了让新加入数据后 B+树的叶子节点还能保持有序,它就需要往叶子结点的中间找,查找过程的时间复杂度是 O(lgn)。如果这个也被写满的话,就需要进行页分裂。页分裂操作需要加悲观锁,性能非常低。

不过, 像分库分表这类场景就不建议使用自增 id 作为主键,应该使用分布式 ID 比如 uuid 。

相关阅读:数据库主键一定要自增吗?有哪些场景不建议自增?

g.不建议使用 NULL 作为列默认值。

NULL''(空字符串)是两个完全不一样的值,区别如下:

  • NULL 代表一个不确定的值,就算是两个 NULL,它俩也不一定相等。例如,SELECT NULL=NULL的结果为 false,但是在我们使用DISTINCT,GROUP BY,ORDER BY时,NULL又被认为是相等的。
  • ''的长度是 0,是不占用空间的,而NULL 是需要占用空间的。
  • NULL 会影响聚合函数的结果。例如,SUMAVGMINMAX 等聚合函数会忽略 NULL 值。 COUNT 的处理方式取决于参数的类型。如果参数是 *(COUNT(*)),则会统计所有的记录数,包括 NULL 值;如果参数是某个字段名(COUNT(列名)),则会忽略 NULL 值,只统计非空值的个数。
  • 查询 NULL 值时,必须使用 IS NULLIS NOT NULLl 来判断,而不能使用 =、!=、 <、> 之类的比较运算符。而''是可以使用这些比较运算符的。

尽量用 UNION ALL 代替 UNION

UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作,更耗时,更消耗 CPU 资源。

UNION ALL 不会再对结果集进行去重操作,获取到的数据包含重复的项。

不过,如果实际业务场景中不允许产生重复数据的话,还是可以使用 UNION。

优先使用批量操作

对于数据库中的数据更新,如果能使用批量操作就要尽量使用,减少请求数据库的次数,提高性能。

# 反例
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 426547, 'user1');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 33, 'user2');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 293854, 'user3');

# 正例
INSERT into `cus_order` (`id`, `score`, `name`) values(1, 426547, 'user1'),(1, 33, 'user2'),(1, 293854, 'user3');

Show Profile 分析 SQL 执行性能

为了更精准定位一条 SQL 语句的性能问题,需要清楚地知道这条 SQL 语句运行时消耗了多少系统资源。 SHOW PROFILESHOW PROFILES 展示 SQL 语句的资源使用情况,展示的消息包括 CPU 的使用,CPU 上下文切换,IO 等待,内存使用等。

MySQL 在 5.0.37 版本之后才支持 Profiling,select @@have_profiling 命令返回 YES 表示该功能可以使用。

 mysql> SELECT @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES              |
+------------------+
1 row in set (0.00 sec)

注意SHOW PROFILESHOW PROFILES 已经被弃用,未来的 MySQL 版本中可能会被删除,取而代之的是使用 Performance Schema。在该功能被删除之前,我们简单介绍一下其基本使用方法。

想要使用 Profiling,请确保你的 profiling 是开启(on)的状态。

你可以通过 SHOW VARIABLES 命令查看其状态:

也可以通过 SELECT @@profiling命令进行查看:

mysql> SELECT @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set (0.00 sec)

默认情况下, Profiling 是关闭(off)的状态,你直接通过SET @@profiling=1命令即可开启。

开启成功之后,我们执行几条 SQL 语句。执行完成之后,使用 SHOW PROFILES 可以展示当前 Session 下所有 SQL 语句的简要的信息包括 Query_ID(SQL 语句的 ID 编号) 和 Duration(耗时)。

具体能收集多少个 SQL,由参数 profiling_history_size 决定,默认值为 15,最大值为 100。如果设置为 0,等同于关闭 Profiling。

如果想要展示一个 SQL 语句的执行耗时细节,可以使用SHOW PROFILE 命令。

SHOW PROFILE 命令的具体用法如下:

SHOW PROFILE [type [, type] ... ]
    [FOR QUERY n]
    [LIMIT row_count [OFFSET offset]]

type: {
    ALL
  | BLOCK IO
  | CONTEXT SWITCHES
  | CPU
  | IPC
  | MEMORY
  | PAGE FAULTS
  | SOURCE
  | SWAPS
}

在执行SHOW PROFILE 命令时,可以加上类型子句,比如 CPU、IPC、MEMORY 等,查看具体某类资源的消耗情况:

SHOW PROFILE CPU,IPC FOR QUERY 8;

如果不加 FOR QUERY {n}子句,默认展示最新的一次 SQL 的执行情况,加了 FOR QUERY {n},表示展示 Query_ID 为 n 的 SQL 的执行情况。

优化慢 SQL

为了优化慢 SQL ,我们首先要找到哪些 SQL 语句执行速度比较慢。

MySQL 慢查询日志是用来记录 MySQL 在执行命令中,响应时间超过预设阈值的 SQL 语句。因此,通过分析慢查询日志我们就可以找出执行速度比较慢的 SQL 语句。

出于性能层面的考虑,慢查询日志功能默认是关闭的,你可以通过以下命令开启:

# 开启慢查询日志功能
SET GLOBAL slow_query_log = 'ON';
# 慢查询日志存放位置
SET GLOBAL slow_query_log_file = '/var/lib/mysql/ranking-list-slow.log';
# 无论是否超时,未被索引的记录也会记录下来。
SET GLOBAL log_queries_not_using_indexes = 'ON';
# 慢查询阈值(秒),SQL 执行超过这个阈值将被记录在日志中。
SET SESSION long_query_time = 1;
# 慢查询仅记录扫描行数大于此参数的 SQL
SET SESSION min_examined_row_limit = 100;

设置成功之后,使用 show variables like 'slow%'; 命令进行查看。

| Variable_name       | Value                                |
+---------------------+--------------------------------------+
| slow_launch_time    | 2                                    |
| slow_query_log      | ON                                   |
| slow_query_log_file | /var/lib/mysql/ranking-list-slow.log |
+---------------------+--------------------------------------+
3 rows in set (0.01 sec)

我们故意在百万数据量的表(未使用索引)中执行一条排序的语句:

SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;

确保自己有对应目录的访问权限:

chmod 755 /var/lib/mysql/

查看对应的慢查询日志:

 cat /var/lib/mysql/ranking-list-slow.log

我们刚刚故意执行的 SQL 语句已经被慢查询日志记录了下来:

# Time: 2022-10-09T08:55:37.486797Z
# User@Host: root[root] @  [172.17.0.1]  Id:    14
# Query_time: 0.978054  Lock_time: 0.000164 Rows_sent: 999999  Rows_examined: 1999998
SET timestamp=1665305736;
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;

这里对日志中的一些信息进行说明:

  • Time :被日志记录的代码在服务器上的运行时间。
  • User@Host:谁执行的这段代码。
  • Query_time:这段代码运行时长。
  • Lock_time:执行这段代码时,锁定了多久。
  • Rows_sent:慢查询返回的记录。
  • Rows_examined:慢查询扫描过的行数。

实际项目中,慢查询日志通常会比较复杂,我们需要借助一些工具对其进行分析。像 MySQL 内置的 mysqldumpslow 工具就可以把相同的 SQL 归为一类,并统计出归类项的执行次数和每次执行的耗时等一系列对应的情况。

找到了慢 SQL 之后,我们可以通过 EXPLAIN 命令分析对应的 SELECT 语句:

mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | cus_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997572 |   100.00 | Using filesort |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

比较重要的字段说明:

  • select_type :查询的类型,常用的取值有 SIMPLE(普通查询,即没有联合查询、子查询)、PRIMARY(主查询)、UNION(UNION 中后面的查询)、SUBQUERY(子查询)等。
  • table :表示查询涉及的表或衍生表。
  • type :执行方式,判断查询是否高效的重要参考指标,结果值从差到好依次是:ALL < index < range ~ index_merge < ref < eq_ref < const < system。
  • rows : SQL 要查找到结果集需要扫描读取的数据行数,原则上 rows 越少越好。
  • ……

关于 Explain 的详细介绍,请看这篇文章:MySQL 执行计划分析。另外,再推荐一下阿里的这篇文章:慢 SQL 治理经验总结,总结的挺不错。

正确使用索引

正确使用索引可以大大加快数据的检索速度(大大减少检索的数据量)。

选择合适的字段创建索引

  • 不为 NULL 的字段 :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
  • 被频繁查询的字段 :我们创建索引的字段应该是查询操作非常频繁的字段。
  • 被作为条件查询的字段 :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
  • 频繁需要排序的字段 :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
  • 被经常频繁用于连接的字段 :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。

避免索引失效

索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:

SELECT * 查询(成本权衡)

  • SELECT * 不会直接导致索引失效。如果 WHERE 条件符合索引规则,索引依然会被使用。
  • 它会导致回表成本增加。如果查询需要的字段不在索引中(非覆盖索引),数据库需要拿着主键回聚簇索引查数据。当数据量较大时,优化器会对比“索引查找 + 回表”与“直接全表扫描”的成本,若前者成本过高,优化器会主动放弃索引选择全表扫描。
  • SELECT * 还会网络传输和数据处理的浪费。尽量只查询需要的字段,利用覆盖索引减少回表。

违背最左前缀原则

  • 最左前缀匹配原则指的是在使用联合索引时,MySQL 会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么 MySQL 就会使用索引来过滤数据,这样可以提高查询效率。
  • 最左匹配原则会一直向右匹配,直到遇到范围查询(如 >、<)为止。对于 >=、<=、BETWEEN 以及前缀匹配 LIKE 的范围查询,不会停止匹配。
  • MySQL 8.0.13 版本引入了索引跳跃扫描(Index Skip Scan,简称 ISS),它可以在某些索引查询场景下提高查询效率。在没有 ISS 之前,不满足最左前缀匹配原则的联合索引查询中会执行全表扫描。而 ISS 允许 MySQL 在某些情况下避免全表扫描,即使查询条件不符合最左前缀。不过,这个功能比较鸡肋, 和 Oracle 中的没法比,MySQL 8.0.31 还报告了一个 bug:Bug #109145 Using index for skip scan cause incorrect result(后续版本已经修复)。个人建议知道有这个东西就好,不需要深究,实际项目也不一定能用上。

失效示例:

-- 索引:(sname, s_code, address)
WHERE s_code = 1;                  -- 跳过最左列 sname,失效
WHERE sname = 'A' AND address = 'Shanghai'; -- 跳过中间列 s_code,仅 sname 走索引
WHERE sname = 'A' AND s_code > 1 AND address = 'Shanghai'; -- 范围查询后,address 失效

在索引列上进行计算、函数或类型转换

  • 索引存储的是字段的原始值。对字段进行操作后,数据库无法利用索引树的有序性,只能全表扫描后计算。
  • MySQL 8.0 支持函数索引,可针对计算后的值建索引,但使用场景有限,首选还是优化 SQL 写法。

失效示例:

WHERE height + 1 = 170;            -- 对索引列进行计算
WHERE DATE(create_time) = '2022-01-01'; -- 对索引列使用函数

优化建议:

WHERE height = 169;                -- 将计算移到等号右边
WHERE create_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-01 23:59:59';

LIKE 模糊查询以通配符开头

  • LIKE 查询必须以具体字符开头才能利用索引有序性,例如 WHERE sname LIKE 'Guide%';
  • 这是因为B+ 树是从左到右排序的。前缀通配符(%)破坏了有序性,无法定位起始点。

失效示例:

WHERE sname LIKE '%Guide';          -- 前缀模糊,全表扫描
WHERE sname LIKE '%Guide%';         -- 前后模糊,全表扫描

OR 连接条件使用不当

  • 如果 OR 两边的列中有一列没有索引,通常会导致整个查询放弃索引,走全表扫描。
  • 确保 OR 两边的列都建有索引,或改写为 UNION ALL

失效示例:

-- 假设 sname 有索引,address 无索引
WHERE sname = '学生 1' OR address = '上海'; -- 索引失效,全表扫描

N / NOT IN 使用不当

  • IN:当 IN 列表中的值太多(通常超过 200 个,由 eq_range_index_dive_limit 参数决定)或查询范围覆盖了太多行,会导致索引失效。
  • NOT IN:在大多数情况下会引发全表扫描,因为它需要证明“不属于”某个集合,这在 B+ 树中通常需要遍历所有叶子节点。

失效示例:

WHERE s_code IN (1, 2, 3 ... 500); -- 列表过长可能失效
WHERE s_code NOT IN (1, 2, 3);     -- 通常失效

隐式类型转换

这是开发中最隐蔽的坑,转换的方向决定了索引的生死。

  • 字段类型为字符串,查询条件未加引号(如 varchar 字段查 WHERE col = 123);或字段类型为数字,查询条件加了引号且字符集不匹配。
  • MySQL 会自动进行类型转换,导致索引列值发生变化,无法匹配索引树。
  • 详细介绍:MySQL隐式转换造成索引失效

ORDER BY 排序优化陷阱

即使 WHERE 条件精准,如果 ORDER BY 处理不好,依然会出现慢查询。

  • 如果查询走了索引 A,但排序要求字段 B,或者需要回表的数据量太大导致优化器放弃索引排序,就会触发 Using filesort(内存/磁盘排序)。
  • 利用覆盖索引同时满足 WHEREORDER BY。例如索引为 (name, age),查询 SELECT name, age FROM users WHERE name = 'A' ORDER BY age 是极其高效的。

最后,总结一个口诀

  • 全值匹配我最爱,最左前缀不能改。
  • 范围之后全失效,函数计算索引败。
  • 模糊首位莫加百分号,类型转换要避开。
  • OR 连接需谨慎,覆盖索引避回表。

被频繁更新的字段应该慎重建立索引

虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。

尽可能的考虑建立联合索引而不是单列索引

因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。

注意避免冗余索引

冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。

考虑在字符串类型的字段上使用前缀索引代替普通索引

前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。

删除长期未使用的索引

删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用

参考

Morty Proxy This is a proxified and sanitized view of the page, visit original site.