mysql索引是否影响插入和更新性能_mysql索引写性能分析

来源:这里教程网 时间:2026-02-28 20:52:20 作者:

索引越多,INSERT 越慢是必然的

每新增一条记录,MySQL 不仅要写数据行,还要同步更新所有相关索引的 B+ 树结构。尤其是二级索引(非主键索引),需要额外回表或维护独立的叶子节点,写放大效应明显。主键索引(聚簇索引)本身和数据存储绑定,插入时可能触发页分裂;而每个二级索引都是一份独立拷贝,

INSERT
操作会按索引列顺序分别插入多棵 B+ 树。

实操建议:

单条
INSERT
语句带多行(如
INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'c');
)比循环执行多次快得多——减少索引批量更新的开销
大批量导入前可临时禁用非必要索引:
ALTER TABLE t DROP INDEX idx_col;
,导入完成再重建
避免在 UUID、随机字符串等高离散度字段上建索引,这类值导致 B+ 树频繁分裂,恶化插入性能

UPDATE 涉及索引列时性能下降最明显

UPDATE
的代价取决于是否修改了索引列。如果只改非索引字段,InnoDB 通常只需定位并修改聚簇索引中的行记录;但一旦
SET
子句里包含索引列(比如
UPDATE t SET status = 1, name = 'new' WHERE id = 100
name
是索引列),MySQL 就必须在对应二级索引中删除旧值、插入新值——相当于一次 delete + insert。

常见错误现象:

执行
UPDATE t SET created_at = NOW() WHERE user_id = 123
,而
user_id
created_at
都有单独索引 → 触发两棵索引树更新
联合索引
(a, b)
上执行
UPDATE ... SET a = ?
→ 整个索引项重定位,开销接近删除+重建
使用
LIKE '%xxx'
或函数索引(如
JSON_EXTRACT()
)做条件更新时,即使没改索引列,也可能因无法走索引导致全表扫描,间接拖慢更新

唯一索引的写入检查带来额外 I/O 开销

普通索引允许重复值,插入/更新时只需定位到位置插入即可;但唯一索引(含主键)必须先做存在性校验——即在 B+ 树中查找该值是否已存在。这个查找过程哪怕命中缓存,也比普通索引多一次逻辑判断和潜在的磁盘读(尤其当索引页不在

innodb_buffer_pool
中时)。

性能影响点:

高并发下多个线程同时插入相同唯一键前缀(如订单号前缀固定),容易引发
duplicate key
冲突等待,甚至死锁
INSERT IGNORE
ON DUPLICATE KEY UPDATE
本质仍是先查后插/更,不省去唯一性检查步骤
自增主键虽避免了唯一性冲突,但若业务层依赖
SELECT MAX(id)+1
手动赋值,则丧失了自增的并发优势,反而引入竞争

如何平衡查询与写入:几个硬指标参考

没有银弹,但可以基于数据特征做取舍。例如日志类表(写多读少)通常只保留主键,最多加一个时间范围查询用的

created_at
索引;用户中心表(读写均衡)则需谨慎评估每个
WHERE
/
ORDER BY
/
JOIN
字段是否真有必要建索引。

实操判断依据:

单表索引总数超过 5–6 个,且写入 QPS > 500,就要警惕写放大问题
SHOW INDEX FROM t
查看
Cardinality
,若某索引基数远低于表总行数(比如
CARDINALITY / TABLE_ROWS ),大概率是低效索引
开启
innodb_monitor_output
或使用
performance_schema.table_io_waits_summary_by_index_usage
,观察哪些索引从不被用于查询却持续消耗写入资源

真正难的是动态场景:有些索引白天支撑报表查询,晚上 ETL 批量更新时却成了瓶颈。这种时候,靠静态分析不够,得结合具体时间段的慢日志和索引访问统计来决策。

相关推荐