MySQL MyISAM表REPLACE阻塞SELECT问题求助(无法改用InnoDB)
分析MyISAM表导出时REPLACE阻塞后续SELECT的问题及解决办法
我来帮你把这个问题掰明白——MyISAM的锁机制和InnoDB完全不是一个路数,尤其是元数据锁(MDL)这块,很容易踩这种“连锁阻塞”的坑。
问题根源拆解
咱们一步步捋清楚为什么会出现这个现象:
- MyISAM的MDL锁规则:读操作(比如你导出用的SELECT、业务上的SELECT)会请求共享MDL锁,而写操作(比如REPLACE,本质是DELETE+INSERT)会请求排他MDL锁。排他锁和共享锁是互斥的,必须等所有共享锁都释放,写操作才能拿到排他锁。
- 导出时的锁持有:你用
--skip-lock-tables导出,mysqldump会跑一个长达5分钟的SELECT来拉取全表数据,这个SELECT在整个执行过程中都会死死攥着共享MDL锁不放。这时候你发起REPLACE,它要拿排他锁,自然就只能排队等,状态就是waiting for metadata lock。 - MDL队列的“写优先”逻辑:这是最关键的点——MySQL的MDL等待队列不是“先来后到”这么简单。当队列里已经有一个排他锁请求(就是你的REPLACE)在等的时候,后续新来的共享锁请求(业务SELECT)不会被直接放行,而是会排在这个REPLACE后面一起等。MySQL这么设计是为了防止写操作被源源不断的读操作“饿死”,但在你的场景里,就导致了REPLACE启动后,所有新SELECT都跟着阻塞的情况。
解决办法(贴合你不能换InnoDB的前提)
1. 分批次导出,缩短单个SELECT的锁持有时间
这是最推荐的方案,既能保证导出正常,又能让后续SELECT不被长时间阻塞。把原本一次性导出20GB大表的操作,拆成多个小批次的SELECT,比如按主键、时间戳这类有序字段分块,每次只导出一小部分数据。举个例子:
# 按主键ID分块,每次导出10000条,循环执行直到导出全部数据 mysqldump -u your_user -p your_db your_table --where="id >= 0 AND id < 10000" >> full_dump.sql mysqldump -u your_user -p your_db your_table --where="id >= 10000 AND id < 20000" >> full_dump.sql # ...以此类推
每个小批次的SELECT几秒就能跑完,跑完就释放共享MDL锁。REPLACE就能在这些批次的间隙快速抢到排他锁,后续的SELECT也不会因为队列里有长时间等待的写请求而被阻塞。
2. 改用--lock-tables导出(需权衡写阻塞时长)
如果你能接受导出期间REPLACE这类写操作被完全阻塞,但要求业务SELECT全程正常,可以去掉--skip-lock-tables,用默认的--lock-tables选项导出。mysqldump会给表加一个表级读锁(READ LOCK):
- 所有业务SELECT都能正常获取共享锁,不会被阻塞;
- REPLACE这类写操作会进入等待,但后续的SELECT不会跟着阻塞(因为表级读锁是共享的,新SELECT直接拿锁就行)。
缺点是整个导出的5分钟里,写操作都没法执行,得看你的业务能不能容忍这段时间的写阻塞。
3. 临时调整MDL等待超时(仅应急用)
这个方案不推荐长期用,只能临时救急。你可以调整lock_wait_timeout参数,缩短写操作的MDL等待时间,避免后续SELECT被卡太久:
SET GLOBAL lock_wait_timeout = 60; -- 比如设置成60秒,根据你的业务情况调整
但这是全局参数,会影响所有数据库操作,可能导致其他正常的写操作因为超时失败,所以不到万不得已别用。
内容的提问来源于stack exchange,提问作者Nisalon
相关产品推荐
相关产品推荐

