索引不是白送的。你给表加一个索引,读的时候省了扫描,写的时候就得替它维护一棵树——数据库索引写入变慢是躲不掉的代价,问题只在于这个代价有多大、你能不能接受。指望调两个参数就把写入拉回建索引之前的水平,基本是做梦;真能明显改善的,往往是索引本身建得就不对,或者一批该合并的写被拆成了一条条提交。

数据库索引写入变慢是躲不掉的代价,先看这笔钱花在哪
一条 insert 进 InnoDB,落的不只是聚簇索引那一行。表上有几个二级索引,就得多改几棵树:定位叶子页、改记录、写 redo、把页标脏。页写满了还要分裂,一分裂就是两页的活,连带上层指针也要动。update 碰上被索引的列更麻烦,等于删一遍再插一遍,索引越多,重复动作越多。业内管这叫写放大,听着玄,拆开看就是一句话——一次业务写入,底下干了好几次活。
所以别把写入变慢当成故障。它是设计里明码标价的成本,只是账单不在建索引那天送到你手上,而是在数据量涨起来、并发上来之后才慢慢显形。
那就把索引全删了?读那边立刻还你颜色
走极端最省事也最蠢。索引删光的表,查询全表扫,扫描行数上去了,锁的时间和范围也跟着上去,一台机器上几个慢查询叠着跑,写入队列一样堵。要减的是没人用的索引和重复的索引,不是所有索引。
举个常见的:一个 (user_id, created_at) 的联合索引,很多时候比 user_id 单列加 created_at 单列两个索引加起来更划算——写的时候少维护一棵树,读的时候还能顺手覆盖排序。能不能真覆盖,拿 explain 看一眼,别凭感觉拍板。
主键乱序,是写入身上最隐蔽的一笔税
自增主键的插入基本都在最右边追加,页填充整齐,缓存也友好;换成随机字符串当主键,每次插都可能落到中间某个页,页分裂和缓存命中率下降跟着来,数据量越大越明显。业务上确实需要全局唯一标识,也尽量让它有序,或者把它当业务唯一键、主键仍旧用自增。这条不是理论,把一张写入量大的表从随机串主键改成自增,写延迟的变化通常肉眼可见,改之前先在测试库上比一比。
唯一索引和普通索引,写入时干的活不一样
唯一索引插入前要确认没有冲突,页不在内存里的时候也得把页读进来核对;普通二级索引宽松些,可以先记在 change buffer 里,等页被读上来时再合并。所以除非业务真的要求唯一,别顺手加 unique,尤其是那种“反正加了也不亏”的心态,亏在写入上。
change buffer 也不是免单。机器重启、刷脏、页被读上来的时候,账还是要还,它做的只是把尖峰摊平,让写入别在同一秒全砸到磁盘上。
批量写比调参管用,但别一口吃成胖子
一条一条 commit,每次都要等日志落盘,网络来回也白搭。把能合并的写合成一批,提交频率降下来,整体吞吐自然上去。但批次别无限放大:事务太大,undo 涨得快、锁持有时间长,真出问题时回滚也慢得难受。导入历史数据这种场景,临时调低刷盘频率确实快不少,代价是掉电可能丢掉最后一段事务,值不值自己衡量,参数的确切含义和影响以官方文档为准。
怎么确认慢在索引维护,而不是别的地方
别一上来就盯 CPU。iostat 看 util 和 await,写延迟高而 CPU 不高,多半是在等磁盘;show engine innodb status 里看有没有堆积的写和刷脏进度;performance_schema 里看是不是卡在锁等待或者行锁冲突。想找没用的索引,MySQL 的 sys 库里有现成视图,但它统计的是实例启动以来的使用情况,重启会清零,结论要结合业务周期看,别拿一小时的数据下结论。
几个容易踩的小地方
字符串列建索引给个前缀长度,把整列长文本塞进索引里,写入和存储两头吃亏;冗余索引最典型的是 (a) 和 (a,b) 同时存在,前者基本可以去掉;主从架构里主库的索引维护开销到从库还要再走一遍,回放延迟有时候就是这么攒出来的;还有统计信息的更新、函数索引、全文索引,都会在写入路径上多多少少掺一脚。
对了,别忽略大事务和长连接。有些写入慢根本不在索引身上,是一个事务开着不提交,把行锁攥了半天,后面排队的全在等。查索引之前先看看有没有这种钉子户。
真要动手,顺序大致是这样:先翻慢查询日志确认哪些索引确实在被用,再对着表结构找冗余和重复,然后检查主键是不是乱序,最后一档才去动批量提交和刷盘参数。反过来做也常见——参数先调一遍,该删的索引没删,过两天又慢回来,白折腾一场。写入性能这事没有一键方案,只有取舍。
【版权声明】:本站内容若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!