SQL UPDATE语句通常不锁整表,但锁行为取决于数据库类型、索引、隔离级别等:MySQL InnoDB默认行锁,无索引时可能全表扫描并升级为间隙锁;PostgreSQL基于MVCC仅锁更新行;SQL Server按数据量自动选择锁粒度。

SQL UPDATE 语句本身不会直接锁整张表,但是否锁表、锁什么、锁多久,取决于数据库类型、存储引擎、事务隔离级别、WHERE 条件是否命中索引、以及是否有触发器或外键约束等。关键不是“会不会锁表”,而是“在什么条件下会升级为表级锁”,以及“通常锁的是行还是页/表”。
MySQL(InnoDB 引擎):默认行级锁,但可能升级
InnoDB 默认对满足 WHERE 条件的**已存在记录加行锁(Record Lock)**,前提是 WHERE 中的列有有效索引(包括主键、唯一索引、普通索引)。若条件未命中索引(如全表扫描),InnoDB 可能对扫描到的每行加锁,甚至因锁数量过多而升级为**间隙锁(Gap Lock)或临键锁(Next-Key Lock)**,进而阻塞其他会话的插入/更新。
- 无索引 WHERE:UPDATE user SET name='A' WHERE phone='138...';(phone 无索引)→ 全表扫描 → 每行加锁 → 实际效果接近“逻辑上锁表”
- 主键更新:UPDATE user SET age=25 WHERE id=1001; → 仅锁 id=1001 这一行
- 范围更新:UPDATE user SET status=1 WHERE create_time > '2024-01-01';(create_time 有索引)→ 加 Next-Key Lock,锁住匹配区间及间隙,防止幻读
- 显式锁表:只有使用 LOCK TABLES user WRITE; 后才真正锁表,UPDATE 本身不触发该行为
PostgreSQL:MVCC + 行级锁,基本不锁表
PostgreSQL 使用多版本并发控制(MVCC),UPDATE 实际是“标记旧行为过期 + 插入新版本行”,因此不阻塞 SELECT(快照读),也不锁整表。它会对被更新的**每一行加 RowExclusiveLock**,但该锁与 SELECT 的 AccessShareLock 兼容,不影响查询。
- 并发 UPDATE 同一行时才会阻塞(等待行锁释放)
- 即使 WHERE 条件无索引,也只锁扫描到的匹配行,不会锁全表
- 表级 DDL(如 ALTER TABLE)需要 AccessExclusiveLock,此时会被正在运行的 UPDATE 阻塞,但 UPDATE 本身不主动获取表锁
SQL Server:按情况选择行锁、页锁或表锁
SQL Server 默认基于访问路径和数据量自动选择锁粒度(行、页、表)。小数据量且索引良好时倾向行锁;当更新大量行(如超过 5000 行)或统计信息不准导致优化器误判,可能升级为页锁甚至表锁(Lock Escalation)。
- 可通过 SET LOCK_ESCALATION = DISABLE 禁用锁升级(需谨慎)
- 使用 WITH (ROWLOCK) 提示可建议行锁,但不保证生效(如索引缺失仍可能升级)
- 查看锁:SELECT * FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID()
避免意外锁表现象的实用建议
多数“UPDATE 像锁表”的问题源于设计或执行不当,而非数据库机制缺陷:
- 务必为 UPDATE 的 WHERE 字段建立合适索引,避免全表扫描
- 批量更新拆分为小批量(如每次 1000 行),减少单次锁持有时间
- 事务中 UPDATE 后尽快提交或回滚,避免长时间持锁
- 测试时用 EXPLAIN(MySQL)、EXPLAIN (ANALYZE)(PostgreSQL)、SET STATISTICS XML ON(SQL Server)确认执行计划是否走索引
- 高并发场景下,考虑用乐观锁(如 version 字段)替代长事务悲观锁










