我的 MySQL 数据库为三个 Web 应用程序提供存储后端服务。然而,我最近永久遇到了错误“等待表元数据锁”。这种情况几乎一直发生,我不明白为什么。
mysql> show processlist -> ; +------+-----------+-----------------+------------+---------+------+---------------------------------+------------------------------------------------------------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+-----------+-----------------+------------+---------+------+---------------------------------+------------------------------------------------------------------------------------------------------+ | 36 | root | localhost:33444 | bookmaker2 | Sleep | 139 | | NULL | | 37 | root | localhost:33445 | bookmaker2 | Sleep | 139 | | NULL | | 38 | root | localhost:33446 | bookmaker2 | Sleep | 139 | | NULL | | 39 | root | localhost:33447 | bookmaker2 | Sleep | 49 | | NULL | | 40 | root | localhost:33448 | bookmaker2 | Sleep | 139 | | NULL | | 1315 | bookmaker | localhost:34869 | bookmaker | Sleep | 58 | | NULL | | 1316 | root | localhost:34874 | bookmaker3 | Sleep | 56 | | NULL | | 1395 | bookmaker | localhost:34953 | bookmaker | Sleep | 58 | | NULL | | 1396 | root | localhost:34954 | bookmaker3 | Sleep | 46 | | NULL | | 1398 | root | localhost:34956 | bookmaker3 | Query | 28 | Waiting for table metadata lock | CREATE TABLE IF NOT EXISTS LogEntries ( lid INT NOT NULL AUTO_INCREMEN | | 1399 | root | localhost | NULL | Query | 0 | NULL | show processlist | +------+-----------+-----------------+------------+---------+------+---------------------------------+------------------------------------------------------------------------------------------------------+
当然可以杀死相应的进程。但是,如果我重新启动尝试创建数据库“bookmaker3”的表结构的程序,新创建的进程将再次处于 Metallock 中。
我什至无法删除数据库:
mysql> drop database bookmaker3;
这也会产生金属锁。
如何修复这个问题?
不幸的是,所接受的解决方案是错误的。说的一点都没错
这确实是(几乎肯定;见下文)要做的事情。但随后它表明,
...而1398不是与锁的连接。怎么会这样? 1398 是正在等待锁定的连接。这意味着它还没有获得锁,因此杀死它没有任何作用。持有锁的进程仍将持有锁,并且下一个线程尝试执行某些操作将因此也停止并按适当的顺序进入“等待元数据锁”。
您无法保证“等待元数据锁定”(WFML) 的进程也不会阻塞,但您可以确定仅终止 WFML 进程将完全无济于事。
真正的原因是另一个进程正在持有锁,更重要的是,
SHOW FULL PROCESSLIST
不会直接告诉你是哪个进程.您可以确定的一件事是,没有标记为“等待元数据锁定”的进程。可以说,这些人是受害者。
SHOW FULL PROCESSLIST
WILL 告诉您进程是否正在做某事,是的。通常它会起作用。在这里,持有锁的进程什么都不做,并且隐藏在其他也不做任何事情的线程中并报告为“睡眠”。如果
SHOW FULL PROCESSLIST
向您显示一个正在运行DML的进程,或者处于“发送数据”状态,那么,那几乎肯定是罪魁祸首。其他进程正在等待它释放锁(它们可以是隐式锁;进程根本不需要发出 LOCK TABLE,这实际上会以不同的方式锁定)。但是一个进程可以在不执行任何操作时持有锁,并被适当地标记为“睡眠”。在OP的情况下,罪魁祸首几乎肯定是进程1396,它在进程1398之前启动,现在处于
睡眠
状态,并且已经已经持续了 46 秒。由于 1396 显然已经完成了它需要做的所有事情(事实证明它现在正在休眠,并且已经这样做了 46 秒,就 MySQL 而言),没有线程进入在它可以持有锁并仍然持有它之前睡眠(否则 1396 也会停止)。由于MySQL的“无死锁”锁定策略,任何进程都不能持有锁、释放锁并再次恢复锁;因此,锁等待总是由仍然持有锁且之前从未持有过该锁的进程引起。这很有用(我们将在下面利用这个事实),因为它可以保证锁“队列”是顺序的。
重要:如果您以受限用户身份连接到 MySQL,
SHOW FULL PROCESSLIST
将不会显示所有进程。因此锁可能由您看不到的进程持有。所以:如果
SHOW FULL PROCESSLIST
向您显示所有内容并显示一个正在运行进程,那么该进程可能是负责的,您需要等待它完成它正在做的任何事情(或者你可以杀死它 - 后果自负)。这个答案的其余部分涉及一种令人困惑的情况,即进程正在等待没有明显的原因并且似乎没有人在做任何事情。
更好的
显示进程列表
上面可以调整为仅显示处于 SLEEP 状态的进程,并且无论如何它都会按时间降序对它们进行排序,因此更容易找到挂起的进程(由于顺序,通常是
在“等待元数据锁定”之前立即睡眠一个;并且它总是比任何等待的睡眠时间都多的睡眠之一。
重要的事情
保留任何“等待元数据锁定”进程单独。
快速而肮脏的解决方案,不真正推荐,但很快
杀死同一数据库上所有处于“睡眠”状态的进程,这些进程比最旧的线程处于“等待元数据锁定”状态。这就是Arnaud Amaury 会这么做:
KILL
后,重新评估情况并相应地重新启动进程。正在等待的进程现在可能正在运行,或者它们可能已经短暂运行并现在正在休眠。 他们现在甚至可能持有新的元数据锁。一百次中有九十九次,要杀死的线程是处于睡眠状态且比等待元数据锁定的较旧线程更老的线程中最年轻的线程: