EFCore迁移是否会锁定Postgres Schema?如何避免迁移引发大表操作服务超时
核心结论
EFCore迁移本身不会主动对整个数据库Schema加全局锁,你碰到的服务全量超时、无响应的问题,根源是迁移脚本最终生成的Postgres DDL语句触发了高等级表锁,叠加1.2亿行大表的锁等待队列特性,把所有正常业务读请求全部阻塞。
你写的迁移代码对应的实际锁影响
EFCore只是把你写的C#迁移逻辑转成对应DDL,本身不额外加锁,每段代码执行时在PG上的锁行为如下:
- 两个
AlterColumn把字段改成可空的操作:会生成ALTER TABLE ... ALTER COLUMN ... DROP NOT NULL语句,执行瞬间需要获取目标表的ACCESS EXCLUSIVE锁——这是PG里等级最高的锁,和所有其他锁类型完全互斥,包括普通SELECT请求持有的ACCESS SHARE读锁。 AddForeignKey操作:这是造成长时间服务不可用的核心原因。EFCore默认生成的外键创建逻辑,会在1.2亿行的大表上做全表扫描校验存量数据的外键匹配性,整个校验过程全程持有ACCESS EXCLUSIVE锁,持锁时长会达到数分钟。- 两个
DropColumn操作:本身PG删列只修改系统表元数据,持锁时间极短,但如果排在前面的DDL已经触发锁等待队列,就会跟着被阻塞,反过来进一步拉长整个锁风暴的持续时间。
之所以会出现100%请求失败的情况,是因为PG的锁等待队列遵循先进先出规则:当第一个高等级的排他锁请求排队等待旧业务读请求释放锁时,后续所有新来的SELECT请求,哪怕锁等级远低于排他锁,也会排在这个DDL请求后面依次等待,直接把整个表的请求队列堵死,直到所有排队请求超时触发失败。
大表迁移避免线上故障的实操方法
- 拆分迁移事务,不要把所有DDL塞在同一个事务里执行:
- 改列可空、删列这类仅修改元数据的操作,单独拆成短事务跑,这类操作本身持锁时间通常小于10ms,不会触发长时间锁等待。
- 创建外键时替换EFCore默认的全表校验逻辑:手动调整迁移里的FK创建代码,先生成不做即时校验的外键约束,对应SQL是
ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY ... NOT VALID,这个语句仅需要极短时间持有ACCESS EXCLUSIVE锁完成元数据修改,不会触发全表扫描;锁释放后,再单独执行VALIDATE CONSTRAINT语句,这个阶段会用低等级的SHARE UPDATE EXCLUSIVE锁做全表扫描,完全不阻塞业务的SELECT和普通写入操作。
- 给所有DDL操作加锁等待超时限制:在每个迁移执行的SQL最开头设置
SET lock_timeout = '2s',如果DDL语句2秒内拿不到需要的锁,就直接报错退出,不会傻等在队列里堵死后续所有业务请求。等业务低峰期、表上没有长事务持有旧锁的时候,再重跑带超时的DDL,因为本身操作时长极短,拿锁后几秒内就会释放,不会对业务造成可感知的影响。 - 坚持手工编写迁移脚本的正确做法,同时把每段生成的SQL提前拿到和线上数据量对齐的测试环境执行,逐句观察锁持有时长,把会触发全表长持锁的逻辑全部替换成分阶段执行的版本。
内容的提问来源于stack exchange,提问作者Maciej Pszczolinski
相关产品推荐
相关产品推荐

