MySQL:永久获取“等待表元数据锁定”

时间:2013-11-05 23:27:25

标签: mysql process blocking

我的MySQL数据库提供三个webapps作为存储后端。但是我最近遇到了错误“等待表元数据锁”。它几乎一直发生,我不明白为什么。

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”的表结构的程序,则新创建的进程将再次在metalock中结束。

我甚至无法删除数据库:

mysql> drop database bookmaker3;

这也产生了一个金属锁。

如何修复?

3 个答案:

答案 0 :(得分:19)

用锁定杀死连接

Kill 1398

然后检查是否通过

将自动提交设置为0
select @@autocommit;

如果是,您可能会忘记提交交易。然后另一个连接想要对这个表执行某些操作,这会导致锁定。

在您的情况下:如果您对 LogEntries (存在whitch)进行了一些查询并且没有提交它,那么您尝试执行CREATE TABLE IF NOT NOT EXISTS来自另一个连接 - 发生元数据锁定。< / p>

修改 对我来说,错误就在你的应用程序的某个地方。检查那里,或者如果你没有在申请中使用交易,请将 autocommit设置为1

ps也查看这篇文章:

答案 1 :(得分:5)

不幸的是,接受的解决方案是错误。就说对了,

使用锁定终止连接

这确实是(肯定是几乎;请参见下文)该做什么。但这暗示了

Kill 1398

...和1398 不是与锁的连接。怎么会这样? 1398是等待锁的连接。这意味着它还没有锁,因此,杀死它毫无用处。持有锁的进程仍将持有该锁,尝试执行某操作的 next 线程也会因此 停顿并按适当的顺序输入“等待元数据锁”。 / p>

您不能保证“等待元数据锁定”(WFML)的进程也不会阻塞,但是您可以肯定,仅杀死WFML进程将完全没有任何效果。

>

真正的原因是另一个进程正在锁定该锁,更重要的是,SHOW FULL PROCESSLIST 不会直接告诉您它是哪个

告诉您该过程是否正在,是的。通常它可以工作。在这里,持有锁的进程什么都不做,在其他线程中也隐藏了什么。

在这种情况下,罪魁祸首是 进程 1396 ,该进程始于进程1398之前,现在处于Sleep状态,已持续46秒。自1396年以来,它显然已经做了所有需要做的事情(事实证明,它现在正在睡眠,并且已经做了46秒钟,就MySQL而言,是 ),在此之前没有线程可以拥有一把锁(或者1396也将停滞)。

重要:如果您以受限用户身份连接到MySQL,则SHOW FULL PROCESSLIST显示所有进程。因此,锁可能是由您看不到的进程持有的。

更好的SHOW PROCESSLIST

SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
    FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
    AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
    ORDER BY `DB`, `TIME` DESC

可以将上面的内容调整为仅显示处于SLEEP状态的进程,并且无论如何它将按时间降序对它们进行排序,因此更容易找到挂起的进程(应该是Sleep紧接在“等待元数据锁定”之前。)

快速又肮脏的解决方案,不推荐使用

在同一数据库上,处于“休眠”状态的

杀死所有所有进程,比处于“等待元数据锁定”状态的最早的线程早。 Arnaud Amaury将会这样做:

  • 对于每个在WaitingForMetadataLock中具有至少一个线程的数据库:
    • 该数据库上WFML中最旧的连接原来是Z秒旧
    • 该DB上早于Z的所有“睡眠”线程都必须进入。从最新的开始,以防万一。
    • 如果该数据库上存在一个较旧且处于非睡眠状态的连接,则可能是那个持有锁的连接,但它正在执行某些操作。您当然可以杀死它,但是特别是如果它是UPDATE / INSERT / DELETE,则这样做后果自负。

一百分之九十九中,处于休眠状态的线程中,被杀死的线程是最年轻的,其年龄比等待元数据锁定的年龄较大的线程

TIME     STATUS
319      Sleep
205      Sleep
 19      Sleep                      <--- one of these two "19"
 19      Sleep                      <--- and probably this one(*)
 15      Waiting for metadata lock  <--- oldest WFML
 15      Waiting for metadata lock
 14      Waiting for metadata lock

(*)TIME顺序实际上有毫秒,所以有人告诉我,它只是不显示它们。因此,虽然两个进程的时间值均为19,但最低的应该更年轻。

重点突出的修复程序

运行SHOW ENGINE INNODB STATUS,然后查看“交易”部分。您会发现类似

TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;

现在,您用SHOW FULL PROCESSLIST检查线程ID 1396与#1701事务的关系。它可能处于“睡眠”状态。因此:具有活动锁的活动事务(#1701),由于具有撤消日志条目,甚至进行了一些更改...但当前处于空闲状态。 ,您不需要杀死其他线程。丢失这些更改。

请记住,在MySQL中什么也不做通常并不意味着什么都不做。如果您从MySQL获得一些记录并为FTP上传建立CSV,则在FTP上传期间,MySQL连接处于空闲状态。

实际上,如果使用MySQL的进程和MySQL服务器的进程在同一台计算机上,并且该计算机运行Linux,并且您具有root特权,则可以通过一种方法找出哪个进程具有请求的连接锁。反过来,这可以(从CPU使用率或最坏的情况来看,strace -ff -p pid)确定该进程是否真的在做某事,以帮助确定是否可以安全终止。

为什么会这样?

我看到这种情况发生在使用“持久”或“池化” MySQL连接的Web应用程序上,这通常节省了很少的时间:Web应用程序实例终止了,但是连接没有,因此它的锁定仍然活着...并封锁了其他所有人。

如果您使用的是最新的MySQL,但不是太新的,则是导致罪魁祸首的另一种方法,因为这将不建议使用,(您需要再次在信息模式)

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
     WHERE LOCK_TRX_ID IN 
        (SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);

实际解决方案

问题通常是由以下架构引起的:

 webapp (jar, php) --> container or application pool (mysqldb, php_module, fastcgi...) --> web server --> MySQL

当webapp死亡或webapp轻量级线程实例死亡时,容器可能不会。保持连接打开的是容器,因此显然连接不会关闭。可以预见,MySQL认为该操作没有完成。

如果webapp自身没有清理(没有ROLLBACKCOMMIT进行交易,没有UNLOCK TABLES等),那么该webapp开始做的一切仍然存在,并且可能仍在阻止其他所有人。

那么有两种解决方案。更糟糕的是lower the idle timeout。但是,如果您在两个查询之间等待太久(确切地说:“ MySQL服务器已消失”。或者可以使用mysql_ping(如果可用)(很快就会弃用。There are workarounds用于PDO),那么会发生什么)。 em>或,您可能会检查那个错误,然后重新打开连接(这是Python方式)。因此-只需支付少量的性能费用-即可。

更好,更智能的解决方案不太容易实现。尽力使脚本本身干净,确保捕获所有异常并正确处理它们,或者在可能的情况下完全跳过持久连接。让每个实例创建自己的连接或use a smart pool driver(在PHP PDO中,使用显式设置为PDO::ATTR_PERSISTENT的{​​{1}})。另外(例如,在PHP中),您可以让销毁和异常处理程序通过提交或回滚事务以及发出显式表解锁来强制清除连接。

答案 2 :(得分:0)

如果您有HS插件并尝试CREATEALTER表已经尝试通过HS进行评估,您将遇到类似的问题,您必须以这种方式重新启动HS插件发布表元数据锁:

UNINSTALL PLUGIN HANDLERSOCKET;
INSTALL PLUGIN HANDLERSOCKET SONAME 'handlersocket.so';