MySQL中UPDATE加WHERE但未走索引会触发全表扫描,导致行锁升级为表锁,引发阻塞;PostgreSQL虽默认行锁,但全表扫描仍严重拖慢性能;务必用EXPLAIN验证索引命中,WHERE字段需有单列或复合索引最左前缀。

UPDATE 语句加 WHERE 条件但没走索引会锁表
MySQL 的 UPDATE 在没有有效索引支撑的 WHERE 条件下,可能触发全表扫描 + 行级锁升级为表级锁(尤其在 READ-COMMITTED 以下隔离级别),导致其他查询长时间阻塞。PostgreSQL 虽默认行锁,但全表扫描仍会显著拖慢事务并吃光 shared_buffers。
实操建议:
- 执行前用
EXPLAIN UPDATE ...(MySQL)或EXPLAIN (ANALYZE) UPDATE ...(PostgreSQL)确认是否命中索引 - WHERE 字段必须有单列索引或复合索引的最左前缀;比如
WHERE status = 'pending' AND created_at ,索引应建在(status, created_at)而非仅created_at - 避免在 WHERE 中对字段做函数操作,如
WHERE DATE(created_at) = '2024-01-01'—— 会跳过索引
分批更新时 LIMIT 不是万能解药
MySQL 支持 UPDATE ... LIMIT N,但 PostgreSQL 不支持带 LIMIT 的 UPDATE(需配合 CTE 或子查询)。更重要的是:单纯靠 LIMIT 分批,若 WHERE 条件匹配行数远超预期,可能漏更或重复更——因为两次查询之间数据可能被其他事务修改。
实操建议:
- MySQL:用主键范围分片,例如
WHERE id BETWEEN 10000 AND 19999,每次递增 10000,比LIMIT更可控 - PostgreSQL:用
UPDATE ... WHERE id IN (SELECT id FROM tbl WHERE ... ORDER BY id LIMIT 1000),确保子查询结果稳定 - 无论哪种方式,更新后检查
ROW_COUNT()(MySQL)或GET DIAGNOSTICS(PG)返回影响行数,不为 0 才继续下一批
事务太大导致 binlog / WAL 膨胀甚至 OOM
一次性更新 50 万行,在 MySQL 中可能生成数百 MB 的 binlog;在 PostgreSQL 中则大幅延长 WAL 归档时间、拖慢 checkpoint。更危险的是:长事务会阻止 vacuum(PG)或 purge(MySQL),导致 undo log 持续增长,最终耗尽磁盘。
实操建议:
- 每批控制在 1000–5000 行,具体看单行数据大小和硬件 I/O 能力;超过 1 万行就明显增加失败风险
- 每批更新后显式
COMMIT,不要包在一个大事务里;应用层加SLEEP(0.1)(MySQL)或pg_sleep(0.05)(PG)缓解主从延迟 - 提前清理无关索引:临时禁用非关键二级索引(MySQL 可
ALTER TABLE ... DISABLE KEYS,PG 可先DROP INDEX再重建)
误更新无法回滚?备份和校验必须前置
再谨慎的 SQL 也挡不住 WHERE 条件写错、测试环境数据偏差、或跨库连错实例。线上执行前没有备份,等于裸奔。
实操建议:
- 执行前用
SELECT COUNT(*)和SELECT * FROM ... LIMIT 5双重验证 WHERE 范围,尤其注意 NULL、时区、字符串大小写 - MySQL:用
mysqldump --where="..."导出待更新数据快照;PG:用pg_dump -t tbl --inserts --where="..." - 更新后立刻抽样比对:比如
SELECT id, col FROM tbl WHERE id IN (123,456,789)看目标字段是否已变更,别只信返回的“OK”
最容易被忽略的是 WHERE 条件里的隐式类型转换——比如把字符串 ID 写成数字,MySQL 可能全表转换比对;还有跨字符集连接时的 collation 冲突,会导致条件失效却无报错。










