根本原因是innodb对所有扫描行(含间隙)加next-key锁,无索引时全表扫描导致锁住海量行与间隙,等效锁表;优化关键在于分批删除配合索引+order by+单调字段锚点,确保可控锁粒度并避免重复扫描。

DELETE大表阻塞查询,根本不是语句写得不对,而是它在干三件高危事:锁住大量行(甚至升级成表锁)、疯狂写日志(redo/binlog)、拖着事务不提交——业务查询一碰就卡在“Waiting for table metadata lock”或“Locked”。解决方向只有一个:不让它一次干太多,也不让它干得“太重”。
MySQL中DELETE没走索引直接锁全表
WHERE条件字段没索引、隐式类型转换(比如WHERE user_id = '123'但字段是INT)、函数包裹(WHERE DATE(create_time) )都会让优化器放弃走索引。结果就是全表扫描,每扫一行加一个行锁+间隙锁,InnoDB很快触发锁升级,变成表级X锁。
- 用
EXPLAIN DELETE FROM t WHERE ...确认是否出现type: ALL或Extra: Using where(没用上索引的典型标志) - 立刻补索引:
CREATE INDEX idx_create_time ON t(create_time),注意复合索引要遵循最左前缀 - 别信“加了索引就安全”——如果WHERE里用了
OR、NOT IN或LIKE '%abc',索引仍可能失效
SQL Server锁升级到TAB X导致查询全阻塞
删5000行以上且没索引时,SQL Server自动把行锁合并为表级排他锁(TAB X),此时所有SELECT、INSERT、UPDATE都会排队等锁。这不是性能问题,是设计机制——锁内存开销太大,引擎主动“断臂求生”。
- 查是否已锁表:
SELECT request_session_id, resource_type, request_mode FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID() AND resource_type = 'OBJECT' AND request_mode = 'X' - 删之前先确保WHERE列有索引,且执行计划里显示的是
Index Seek而非Clustered Index Scan - 分批时必须显式控制事务边界,
TOP (@batchsize)+BEGIN TRAN+COMMIT缺一不可;光靠LIMIT(MySQL)或TOP(SQL Server)不加事务,照样锁住整批扫描范围
Oracle外键无索引让删1条也卡5分钟
主表删1行,Oracle要检查所有关联外键表是否引用该行。如果6张千万级子表的外键列都没索引,那就得对每张子表全表扫描一遍——删1行 = 扫6张大表,IO和CPU直接拉满。
- 查外键索引缺失:
SELECT a.table_name, a.column_name FROM all_cons_columns a JOIN all_constraints c ON a.constraint_name = c.constraint_name WHERE c.constraint_type = 'R' AND NOT EXISTS (SELECT 1 FROM all_ind_columns i WHERE i.table_name = a.table_name AND i.column_name = a.column_name) - 给每个外键列补索引:
CREATE INDEX idx_child_fk ON child_table(fk_col) - 如果业务确认不需要级联校验(比如只是关联查询,不删子表数据),可临时禁用约束:
ALTER TABLE parent_table DISABLE CONSTRAINT fk_name(操作完记得启用)
分批删除却还是阻塞,因为漏掉了“看不见的依赖”
你调好了批次、加了索引、加了休眠,结果SHOW PROCESSLIST里还是看到一堆Waiting for table metadata lock——大概率是其他会话拿着MDL锁不放。最常见的“静默杀手”是监控脚本、备份任务、或者应用层忘了commit的长事务。
- MySQL查谁占着MDL:
SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'db' AND OBJECT_NAME = 't' AND LOCK_STATUS = 'PENDING',再顺着OWNER_THREAD_ID去threads表找源头 - SQL Server查阻塞链:
SELECT blocking_session_id, session_id, wait_type FROM sys.dm_exec_requests WHERE blocking_session_id 0 - 别只盯着DELETE本身——
SELECT COUNT(*) FROM t这种语句在RR隔离级别下也会拿S锁,和你的X锁冲突;binlog_format=STATEMENT时大事务重放慢,从库延迟会反压主库
真正难处理的从来不是DELETE语句怎么写,而是它运行时周围有没有其他事务、索引、外键、监控脚本在悄悄拖后腿。这些依赖不会报错,但会让所有优化努力归零。










