资讯详情

资讯详情

MySQL索引设计五大深坑:从慢查询到死锁的实战避坑指南

1. 内容整体设计与思路拆解1.1 为什么索引问题值得单独写一篇先说个背景。MySQL索引是几乎所有后端开发者的日常话题但真正把它搞明白的人不多。面试的时候讲讲索引原理能答上几句一到生产环境出了慢查询、锁等待、死锁立刻抓瞎。我这几年来回折腾过不少次踩过的坑不算少有的是因为当初建索引太随意有的是因为对索引的底层行为理解不到位还有的是细节到连官方文档都没写明白的边界情况。这篇博客想做的就是把这几个坑掰开揉碎讲清楚。我不是来科普B树结构的这类文章太多了。我想分享的是那些你在本地环境根本复现不出来、一上生产就爆炸的问题以及对应的解决方案。如果你正在维护一个有一定体量的线上MySQL库或者你负责的模块经常出现查询慢、更新卡顿、死锁报警那这篇文章应该对你有用。1.2 我踩坑的典型场景复盘为了让你快速进入状态我先把背景交代清楚。我这边有几套线上业务库单表数据量从几百万到上亿不等用的MySQL版本主要是5.7和8.0。业务类型偏OLTP读多写少但高峰期的写入并发并不低秒杀、拼团、订单状态流转这类场景都有。出现问题的时机基本都集中在两个阶段一是新功能上线前加索引二是大促前做性能压测。这俩阶段都有一个共同点——时间紧、改动急。人一着急就容易只顾着能不能跑通而忽略了上了生产会怎样。结果就是索引建了查询快了但写入变慢了或者索引没问题但应用层SQL写法导致索引根本没被用上再或者两个索引本身都是好的但组合在一起触发了MySQL的锁竞争。这些都是我在实际运维中碰到的真实问题。下面每个坑我都按照问题现象 → 根因分析 → 解决方案 → 避坑建议的结构来讲方便你定位自己是不是也踩了同款。1.3 这5个坑是怎么选出来的我回头看自己记录过的线上问题发现索引相关的故障有个共同规律表面现象各不相同但底层逻辑都指向几个关键点——索引选择性、索引下推、回表、锁机制、排序与分组。也就是说这些坑不是孤立的Bug而是你对索引某个维度的理解存在盲区。所以这篇文章选的5个坑分别对应五个维度第一个坑索引建了但查询依然慢——对应的是索引失效与索引选择性认知不足。第二个坑覆盖索引在特定场景下失效——对应的是回表与索引下推的边界。第三个坑二级索引更新时引发的死锁——对应的是索引与锁的交互机制。第四个坑ORDER BY / GROUP BY 排序走错索引——对应的是排序优化与文件排序。第五个坑索引数量失控导致写放大——对应的是索引维护成本评估。这五个问题组合起来基本覆盖了一个索引从创建、使用、维护到失效的完整生命周期。每一块我都会给出生产环境验证过的方案而不是那种只存在于PPT里的最佳实践。2. 核心细节解析与实操要点2.1 索引选择性为什么有时候建了索引反而更慢先讲第一个坑的最核心知识点索引选择性。很多新手甚至有一定经验的同学建索引时只看这个字段会不会被WHERE用到却忽略了一个关键指标——这个字段的区分度。索引选择性的计算公式很简单SELECT COUNT(DISTINCT col) / COUNT(*) FROM table。如果结果趋近于1说明这个字段的每个值几乎都是唯一的索引效果好如果结果接近0说明这个字段大量重复比如性别、状态这类字段区分度极低B树走这样的索引扫描的条目依然很多甚至不如全表扫描。举个例子。某次我优化一个订单查询接口线上慢查询日志显示一个SQL跑了2秒多SELECT * FROM order_info WHERE status 1 AND created_at 2024-06-01 00:00:00;这个表当时已经有idx_status这个单列索引按说走了索引啊为什么还是慢我看了一下status字段的分布总共5000万行数据status为1的占了接近80%。MySQL优化器计算后发现走idx_status要回表4000万次还不如全表扫一遍再过滤。于是它选择了全表扫描执行计划里的type直接是ALL。这个问题的本质不是索引失效而是优化器认为这个索引没有价值。解决方案也很简单把status和created_at做成联合索引(status, created_at)让B树可以直接在索引层完成范围过滤。改造后SQL的执行计划变成了range扫描行数从4000万降到了几十万耗时降到100ms以内。这里有个容易忽略的细节联合索引的列顺序有讲究。(status, created_at)和(created_at, status)查询效率差别很大。因为联合索引遵循最左前缀原则如果查询条件是status 1 AND created_at ...那status放在前面可以让等值条件先定位到一个较小的范围再通过created_at做范围扫描。反过来把created_at放前面以范围条件开头后面的status索引就派不上用场了。2.2 索引命中的判断手段看懂EXPLAIN说个实操层面的东西。判断一个索引到底有没有生效眼光不能停留在执行完快不快上必须看执行计划。我见过太多人一说SQL慢第一反应是加个索引加了之后发现还慢然后继续加最后表上挂了七八个索引写入性能雪崩。正确的做法是用EXPLAIN关键词查看执行计划。重点关注几个字段字段说明常见问题type访问类型从好到差依次是 system const eq_ref ref range index ALLkey实际使用的索引可能为NULL代表没走索引rows预估扫描行数数值越大越危险是慢查询的直接指标Extra额外信息出现Using filesort、Using temporary时需要警惕如果type是ALL或者index说明全表扫描或全索引扫描如果是range或者ref说明索引查询正常。rows字段尤其关键如果优化器预估扫描行数接近全表行数那索引基本就是无效的。我自己习惯的做法是每次调整索引后先EXPLAIN确认执行计划变了然后再跑真实查询。因为测试环境数据量小有时候直接跑查询根本看不出差别但执行计划的rows变化已经能暴露问题了。2.3 回表与覆盖索引隐藏的性能杀手第二个坑是覆盖索引在某些操作下不覆盖了。这个坑比第一个更隐蔽因为它需要你对InnoDB的索引结构有更细的理解。InnoDB的索引分两类聚簇索引主键索引和二级索引。聚簇索引的叶子节点存的是整行数据二级索引的叶子节点存的是索引列值 主键值。所以如果一个查询要的字段在二级索引里都能找到就不需要回表查聚簇索引这叫覆盖索引查询效率极高。但如果你在查询里多选了一个没有被索引覆盖的字段MySQL就必须回表。问题来了回表本身倒不至于致命致命的是大量回表。举例说明。我有一个订单明细表查询语句长这样SELECT order_id, product_id, price FROM order_detail WHERE order_id 12345;一开始我在order_id上建了单列索引由于要的字段product_id和price不在索引里每次查询都要回表。单条查询没问题但是当你在一个循环里执行上千次这种查询回表开销会被无限放大。优化方案是改成联合索引(order_id, product_id, price)让这三个字段全部落在索引里。这条SQL变成纯粹的索引查询甚至可以把type优化到index级别Extra里还会出现Using index意味着根本不需要读聚簇索引。不过这里要提醒一句覆盖索引不是越多越好。联合索引每加一个字段索引体积就大一圈写入时的维护成本也随之上升。所以绝大多数情况下我会优先把高频查询里的SELECT字段做成覆盖索引而不是把所有字段都塞进去。这个取舍需要结合业务读写的比例来判断。2.4 二级索引更新时的锁交互死锁高发区第三个坑是从MySQL热词搜索趋势里也经常出现的mysql通过二级索引更新时先锁二级索引项再回表锁主键这个时间窗口容易形成交叉。这句话本身就是我对这个坑的总结。先梳理一下InnoDB加锁的基本流程。当我们执行一条UPDATE语句条件命中某个二级索引时InnoDB并不是直接锁主键记录而是先在二级索引对应的索引项上加锁然后再回表到聚簇索引对主键记录加锁。这中间存在两个步骤而两个步骤之间是有时间差的。为什么这个时间差会形成交叉死锁我举一个真实场景。假设有两张业务表用户表和订单表。两条并发事务分别执行如下操作事务A通过idx_user_id更新订单表某条记录它先锁住了二级索引项然后准备回表锁主键记录。事务B同时通过另一条路径比如直接主键更新更新同一条订单记录的主键行接着又尝试更新该记录对应的二级索引项。这时候就出现了一个经典的交叉等待事务A持有二级索引锁等待主键锁事务B持有主键锁等待二级索引锁。两个事务互相等待InnoDB死锁检测机制介入选择回滚其中一个事务但你收到的就是Deadlock found when trying to get lock的报错。这个问题的根因本质上是对不同索引项的加锁顺序不一致。解决方案有以下几种。第一种调整业务层面的操作顺序。让所有更新操作都走同一条索引路径比如统一先通过主键更新再更新二级索引字段保证加锁顺序全局一致就能避免交叉等待。但这个方案在复杂业务场景下往往不可行因为不同接口的查询条件本来就不一样。第二种使用SELECT ... FOR UPDATE在事务开始时就主动锁定目标行。通过锁定主键记录让后续的UPDATE操作不再需要动态回表加锁从而打断先二级索引后主键的加锁顺序。这个方案适合单行更新频率高、并发冲突大的场景。第三种从索引设计上缓解。如果更新操作频繁可以考虑将有高更新频率的字段从二级索引中移除或者改用主键或者唯一键作为更新条件减少二级索引项上的锁竞争。另外还有一个低成本手段开启innodb_deadlock_detect的监控并把MySQL的错误日志落库。死锁发生后第一时间去分析SHOW ENGINE INNODB STATUS输出中的LATEST DETECTED DEADLOCK部分能看到具体冲突的索引和锁模式方便快速定位是哪两张表、哪两个索引在竞争。这块内容我建议每个DBA或者后端负责人提前了解别等线上死锁了再临时翻文档。2.5 ORDER BY排序看似无关实则让索引白建第四个坑是排序场景下的索引失效。这是一个非常容易被忽视的问题WHERE条件走索引没问题但加上排序、分组之后执行计划突然变了。MySQL执行带有ORDER BY的查询时优化器有两种选择一是利用索引的有序性直接按索引顺序读取避免额外排序二是如果索引无法提供所需的排序顺序MySQL会先把查询结果取出来放到内存或者磁盘临时文件中做filesort。filesort听名字好像不严重实际上它是性能杀手。我在生产环境遇到过一个问题某个报表接口查询耗时10秒SQL长这样SELECT user_id, amount FROM pay_record WHERE create_time BETWEEN 2024-07-01 AND 2024-07-31 ORDER BY amount DESC LIMIT 20;表上已有idx_create_time但执行计划显示Extra里有Using filesort。原因是create_time索引只保证了create_time的有序性无法保证amount的有序性。MySQL得先把一个月内的所有记录捞出来再按amount做排序最后取前20条。一个月的数据量大概是200万行排序开销直接让查询变慢。解决方案是建联合索引(create_time, amount)。这样索引在create_time相同的情况下天然按照amount排序MySQL直接从头扫描索引取前20条就结束filesort消失查询耗时降到30ms。再深入一层排序方向的坑也要注意。ORDER BY amount DESC和ORDER BY amount ASC在联合索引里的处理机制不太一样。MySQL 8.0支持降序索引可以显式创建INDEX idx_name ON table (create_time ASC, amount DESC)让索引的存储顺序和查询的排序方向一致。如果在MySQL 5.7里降序索引的支持有限只能靠反向扫描来模拟性能会打折扣。我建议如果你的核心报表查询大量使用降序排序尽早升级到8.0。2.6 索引数量失控写放大与空间膨胀第五个坑是索引过多带来的结构性问题。前面几个坑都是单个索引设计的问题这个坑是整个表的索引架构出了问题。场景是这样的业务迭代快每加一个新查询条件就顺手加一个索引一年下来核心表挂了12个索引。单条查询确实快了但整个表的写入性能开始血崩。原因也很简单每次INSERT、UPDATE、DELETEInnoDB不仅要更新聚簇索引还要同步更新所有二级索引。每多一个索引写入操作就多一份开销。更要命的是索引占用的磁盘空间也在膨胀。我清理过一个表数据本身才3GB12个索引加起来超过5GB翻了快一倍。更隐蔽的问题是索引多了之后优化器选择索引的负担也会加重。有些时候优化器预估成本并不准确选了一个你认为不该选的索引导致查询反而变慢。这个问题的解法不是删掉所有多余索引而是建立一套索引治理机制第一步通过慢查询日志找出高频SQL整理出每个SQL实际使用的索引。第二步用performance_schema或者sys.schema_unused_indexes视图找到从未被使用的冗余索引。第三步评估哪些索引可以被其他联合索引覆盖进行合并或删除。第四步删除索引时要注意DROP INDEX本身会锁表取决于MySQL版本和表大小尽量在低峰期操作。以那张12个索引的表为例我最终通过联合索引合并把索引数量降到了6个。具体操作是把(status)和(status, create_time)合并为(status, create_time)把单独存在的(category_id)和(category_id, status)合并为(category_id, status)删除了一些几乎不会被用到的单列索引。写性能恢复到了正常水平磁盘空间也释放了大约2GB。3. 实操过程与核心环节实现3.1 从慢查询日志定位问题SQL这一步是整个索引优化的入口。没有慢查询日志你就跟盲人摸象一样光靠猜是猜不出索引该怎么建的。我优化线上索引的第一步一定是先去翻慢查询日志。MySQL的慢查询日志默认是关闭的需要手动开启。我一般会在配置文件中这样设置slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 1 log_queries_not_using_indexes 1long_query_time设成1秒任何执行超过1秒的查询都会进日志。log_queries_not_using_indexes能把那些没走索引的查询也记录下来这个特别有用因为很多慢查询不是因为执行慢而是因为索引压根没被用上。拿到慢查询日志之后我习惯用mysqldumpslow做汇总分析。它能聚合同类SQL把时间和次数最靠前的SQL排在最前面非常直观。mysqldumpslow -s at -t 10 /var/log/mysql/slow.log这条命令的意思是按平均耗时排序列出最慢的前10条SQL。看到汇总结果之后再去一条一条执行EXPLAIN逐个判断索引是否合理。3.2 联合索引的设计流程从单列索引到最优组合联合索引的设计是索引优化里含金量最高的部分。我的设计流程一般是这样的第一步把某个表上最高频的查询SQL全部列出来标出WHERE条件里出现的所有等值列和范围列。第二步按照等值列在前、范围列在后的原则排列联合索引的列顺序。为什么要这样排因为B树索引的检索过程依赖前缀有序性等值条件下可以快速定位到具体的一个或者一组值而范围条件只能定位到一个区间。如果把范围列放在前面等值列就没办法参与索引定位后续条件都变成了过滤器而不是定位器。第三步检查查询的SELECT字段是否都能被索引覆盖。如果可以把对应字段也加入联合索引形成覆盖索引减少回表。第四步模拟执行计划对比优化前后的rows和Extra变化。我举个例子。某个接口的查询条件是SELECT id, user_id, amount FROM trade_flow WHERE user_id ? AND status ? AND create_time ? ORDER BY create_time DESC LIMIT 20;初步设计可能是(user_id, status, create_time)。这个索引能同时覆盖WHERE条件和ORDER BY的排序需求。进一步看SELECT字段里的id是主键二级索引里天然带主键user_id和create_time都在索引里唯一缺的amount需要回表。如果这个接口的QPS比较高可以考虑把amount也加入索引做成(user_id, status, create_time, amount)但代价是索引体积变大。这个就需要实际测试一下了不能盲选。3.3 索引创建的完整执行示例为了让内容更具体我放一段完整的索引创建和验证过程。假设有一张pay_order表结构如下CREATE TABLE pay_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint(20) NOT NULL COMMENT 用户ID, status tinyint(4) NOT NULL COMMENT 订单状态, amount decimal(10,2) NOT NULL COMMENT 支付金额, pay_time datetime DEFAULT NULL COMMENT 支付时间, create_time datetime NOT NULL COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;业务上有两个高频SQL一个是按用户查询订单列表另一个是按支付时间统计金额。优化前表上只有主键和唯一键。让我先看看第一个SQLSELECT id, order_no, amount FROM pay_order WHERE user_id 1001 AND status 2 ORDER BY create_time DESC LIMIT 10;不带索引时的EXPLAIN结果type是ALLrows是900万。这肯定不行。创建联合索引ALTER TABLE pay_order ADD INDEX idx_user_status_time (user_id, status, create_time);再次执行EXPLAINtype变成了refrows从900万降到几百行。但Extra里还是有Using filesort因为create_time是范围条件之后的排序字段虽然索引里带了它但排序方向是倒序和索引顺序不完全匹配。这时有两个选择一是在条件下改成ORDER BY create_time ASC再对结果集做反转二是直接把索引的定义改成降序ALTER TABLE pay_order ADD INDEX idx_user_status_time_desc (user_id, status, create_time DESC);MySQL 8.0支持这种写法实测之后Using filesort彻底消失。再来看第二个统计SQLSELECT DATE(pay_time) AS d, SUM(amount) FROM pay_order WHERE pay_time BETWEEN 2024-07-01 AND 2024-07-31 GROUP BY DATE(pay_time);这个SQL如果用pay_time单列索引虽然可以走range但GROUP BY依然需要临时表Extra里会出现Using temporary。这里我建议在pay_time上建单列索引因为GROUP BY是按函数处理后的日期分组即便是联合索引也无法预先排序。所以最优解是idx_pay_time (pay_time)让范围扫描高效分组交给临时表处理。如果数据量实在太大可以再考虑汇总表的方案。3.4 线上执行的注意事项索引操作虽然看着简单但线上执行时有不少坑。ALTER TABLE在MySQL 5.6之前会全程锁表5.6及以后支持了在线DDL但即使如此大表上执行创建索引依然可能对磁盘I/O造成压力。我在线上执行ALTER TABLE ADD INDEX时会遵循几个原则数据量超过千万行的表绝对不在业务高峰期执行索引变更。使用pt-online-schema-change工具来做大表的索引变更它能通过拷贝表的方式在线完成减少锁的影响。执行之前先检查innodb_buffer_pool_size和磁盘空间确保变更期间有足够的资源缓冲。变更完成后立即用EXPLAIN验证执行计划并观察一段时间的慢查询和锁等待指标。3.5 索引治理的落地流程还是回到第五个坑。如果表上索引已经很多了怎么系统性治理我用一套可复制的流程首先列出表上所有索引用SHOW INDEX FROM table查看索引详情。然后开启performance_schema用下面的SQL找出从未用过的索引SELECT * FROM sys.schema_unused_indexes;这个视图是MySQL 5.7及以上自带的如果没开启performance_schema它会返回空值。这时候可以临时开启收集几天的数据再分析。拿到未使用索引清单之后先不急着删。每个索引都要对应到业务SQL里去做交叉验证确认没有低频但关键的接口在用这个索引。比如一个索引可能一个月才被某个月度报表用一次但那个报表是关键链路这种索引不能删。然后做联合索引合并评估。比如idx_status和idx_status_create_time这两个索引就是明确的冗余关系前面的索引完全被后者覆盖可以直接删除前者。我整理过一个简单的评估表格模板你可以参考索引名称包含字段被哪些SQL使用是否冗余处理建议idx_statusstatusSQL_A被idx_status_create_time覆盖删除idx_status_create_timestatus, create_timeSQL_A, SQL_B无冗余保留idx_user_iduser_idSQL_C被idx_user_status_time覆盖删除idx_user_status_timeuser_id, status, create_timeSQL_C, SQL_D无冗余保留这张表做完每次索引评审都有据可依而不是拍脑袋决定。4. 常见问题与排查技巧实录4.1 MySQL 5.7与8.0在索引行为上的差异很多朋友会问这两个版本的索引优化手段能不能通用。从我实际运维的经验来看大部分索引设计原则是通用的但有些细节差异必须注意8.0支持降序索引5.7不支持这是排序优化场景的最大差异。8.0支持INVISIBLE INDEX可以先把索引设为不可见观察一段时间再决定是否删除这是5.7没有的功能对于索引治理来说非常实用。8.0优化器对索引条件下推Index Condition Pushdown, ICP的支持更成熟某些场景下即使索引包含的字段不完整也能在索引层过滤掉部分行减少回表。8.0的utf8mb4默认排序规则是utf8mb4_0900_ai_ci与5.7的utf8mb4_general_ci不同这会影响字符串字段的索引排序效率虽然影响不大但迁移时要注意。如果你还在5.7上我的建议是排序需求较重的业务抓紧评估升级8.0如果暂时无法升级尽量避免在排序字段上使用降序用升序扫描加结果反转的方式替代。4.2 常见错误操作清单把常见的错误操作列一个清单方便自查错误一在小表上过度设计索引。一张只有几千行的配置表任何索引都无所谓全表扫描也很快。强行加索引反而增加维护成本。建索引前先看数据量。错误二在低区分度字段上盲目建索引。性别、状态、是否删除这类字段单独建索引基本没有意义。除非配合其他列组成联合索引。错误三对变量列使用函数导致索引失效。比如WHERE DATE(create_time) 2024-07-01这种写法会让索引完全失效。正确写法是WHERE create_time 2024-07-01 AND create_time 2024-07-02。错误四隐式类型转换。比如字段是varchar但查询用了数字类型MySQL会做隐式转换导致索引失效。确保查询参数类型和字段类型一致。错误五LIKE前置通配符。%keyword%这种模糊查询即便字段有索引也用不上。如果业务确实需要这种查询考虑全文索引或者专门的搜索服务。错误六OR条件导致索引失效。多个条件用OR连接时如果其中一个条件没有索引整个查询可能走全表扫描。可以用UNION ALL拆开写。4.3 死锁问题的排查流程死锁是最难排查的线上问题之一因为没有固定的复现路径。我处理死锁的标准流程如下第一步拿到报错信息后立即去MySQL错误日志里找最近一次死锁的详情。执行SHOW ENGINE INNODB STATUS\G重点关注LATEST DETECTED DEADLOCK部分它会把两个事务各自持有什么锁、等待什么锁都打印出来。第二步分析事务的加锁顺序和操作对象明确是哪两张表、哪几个索引出现了交叉。第三步查看相关业务代码确认这两个操作是否有可能并发执行以及是否跨了多个表操作。通常死锁都发生在多表更新的场景里因为事务A锁了表1的一行再去锁表2的一行事务B却以相反的顺序锁了两张表。第四步调整事务的加锁顺序统一从主键或者同一索引路径去更新记录打破循环等待。第五步如果业务层面的调整成本过高考虑降低事务隔离级别或者把大事务拆成多个小事务缩短锁持有时间。4.4 索引优化后的性能验证模板每次做完索引优化我都会执行一套标准验证流程避免改了索引但没改到位记录优化前的SQL平均耗时和EXPLAIN的rows值。执行ANALYZE TABLE table_name让统计信息更新确保优化器能准确评估新索引的代价。重新执行EXPLAIN确认type、key、rows三个字段都符合预期。用SELECT ... SQL_NO_CACHE执行几次真实查询记录耗时。观察一段时间慢查询日志确认对应的SQL不再出现在慢日志里。在高峰期关注Innodb_row_lock_current_waits和Innodb_row_lock_time_avg确认索引变更没有引入额外的锁竞争。这套流程看起来繁琐但能最大程度避免改了索引问题依旧的情况。4.5 一些不一定写进文档但很实用的经验最后分享几条是在实际运维中反复验证过的经验。这些内容不一定写在官方文档里但实用性很强。第一索引不是越小越好也不是越多越好而是够用最好。判断标准是能被当前所有核心SQL用上且不产生多余的写放大。第二OPTIMIZE TABLE不能随意执行。很多人以为表碎片多了就重建一下但在大表上执行OPTIMIZE TABLE会锁表影响线上服务。数据量超过千万级的表我更倾向于通过pt-online-schema-change来重建表而不是直接OPTIMIZE。第三主键不要用业务字段。用自增ID或者雪花ID做主键可以保证插入时聚簇索引的顺序性避免随机插入导致的页分裂和碎片。第四尽量让主键短。因为二级索引的叶子节点存的是主键值主键越长每个二级索引的体积就越大同样导致写放大和空间膨胀。第五不要忽略SHOW CREATE TABLE的输出。有时候你以为某个字段有索引实际上上次重建表时因为字段改名索引被悄悄删掉了。一切以SHOW CREATE TABLE的输出为准不要凭记忆判断线上表结构。5. 踩坑之后的总结性教训五个坑讲完了如果让我自己总结一条最深的体会那就是索引优化不能靠直觉必须依赖数据和工具。没有慢查询日志没有EXPLAIN没有performance_schema你在线上做索引优化就是赌博。我个人在实际操作中的体会是每一条SQL的索引设计都应该花时间做执行计划分析而不是建完索引就跑一次看快不快。尤其是生产环境数据量一上来很多本地测试看不出来的问题会集中爆发。另外索引治理应该变成一个常态化机制每季度梳理一次核心表的索引情况而不是出了问题才去处理。最后再分享一个小技巧在给联合索引设计列顺序时除了考虑字段区分度和查询条件还有一个容易被忽略的维度——字段的更新频率。如果某个字段频繁被更新把它放在二级索引靠前的位置会导致每次更新都要移动索引项锁竞争更激烈。尽量把更新频率高的字段放在联合索引靠后的位置减少索引结构调整的代价。这些经验是我用多次线上故障换来的。希望你看了之后能少踩几个坑。如果这篇文章里的方案能帮你在生产环境少加一次班我就觉得很值了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →