24

我的 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”的表结构的程序,则新创建的进程再次以金属锁结束。

我什至不能删除数据库:

mysql> drop database bookmaker3;

这也产生了一个金属锁。

如何修复?

4

4 回答 4

24

不幸的是,公认的解决方案是错误的。就它所说的来说是对的,

用锁杀死连接

这确实是(几乎可以肯定;见下文)该做什么。但它暗示,

Kill 1398

...而 1398与锁无关怎么会这样?1398 是等待锁的连接。这意味着它还没有锁,因此杀死它没有任何用处。持有锁的进程仍然会持有锁,因此下一个试图做某事的线程也会停顿并按适当的顺序进入“等待元数据锁”。

您无法保证“等待元数据锁定”(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'ing 紧接在“等待”之前的进程元数据锁”)。

重要的事情

不理会任何“等待元数据锁定”过程

快速而肮脏的解决方案,不是很推荐,但很快

杀死同一数据库上所有处于“睡眠”状态的进程,这些进程比处于“等待元数据锁定”状态的最旧线程更老。这就是Arnaud Amaury会做的事情:

  • 对于在 WaitingForMetadataLock 中至少有一个线程的每个数据库:
    • 该数据库上 WFML 中最旧的连接原来是 Z 秒旧的
    • 该数据库上的所有“睡眠”线程以及比 Z 更早的线程都必须消失。从最新鲜的开始,以防万一。
    • 如果该数据库上存在一个较旧且非休眠的连接,则可能是那个持有锁的连接,但它正在做某事。你当然可以杀死它,但特别是如果它是一个 UPDATE/INSERT/DELETE,你这样做后果自负。

在 100 次中有 99 次,要被杀死的线程是处于睡眠状态且比等待元数据锁的旧线程更老的线程中最年轻的:

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 连接的 webapps 上,现在通常节省很少的时间:webapp 实例终止,但连接没有,所以它的锁仍然存在......并阻止其他人.

我发现的另一种有趣的方式是,在上面的假设中,运行一个返回一些行的查询,并且只检索其中一些。如果查询未设置为“自动清理”(无论底层 DBA 执行此操作),它将保持连接打开并防止表上的完全锁定通过。我在一段代码中遇到了这种情况,该代码通过选择该行并验证它是否出现错误(不存在)(它必须存在)来验证该行是否存在,但没有实际检索该行

询问数据库

如果你有一个最近的 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 app connection pool
   (mysqldb, php_module, fastcgi...) --> 
   --> MySQL

当 webapp 死掉,或者 webapp 轻量级线程实例死掉时,容器/连接池可能不会。并且是容器使连接保持打开状态,因此显然连接不会关闭。可以预见的是,MySQL 并不认为操作已完成

如果 webapp 没有自行清理(noROLLBACKCOMMIT用于事务、 noUNLOCK TABLES等),那么 webapp 开始做的任何事情仍然是 extant,并且可能仍然阻止其他人。

那么有两种解决方案。更糟糕的是降低空闲超时。但是猜猜如果你在两个查询之间等待太久会发生什么(确切地说:“MySQL 服务器已经消失”)。然后,如果可用,您可以使用mysql_ping(很快就会被弃用。PDO 的解决方法。或者您可以检查错误,并在发生时重新打开连接(这是 Python 方式)。所以 - 对于一小笔性能费用 - 它是可行的。

更好、更智能的解决方案实施起来不那么简单。努力让脚本自行清理,确保检索所有行或释放所有查询资源,捕获所有异常并正确处理它们,或者如果可能,完全跳过持久连接。让每个实例创建自己的连接或使用智能池驱动程序(在 PHP PDO 中,使用PDO::ATTR_PERSISTENT显式设置为false)。或者(例如在 PHP 中)您可以让破坏和异常处理程序通过提交或回滚事务并发出显式表解锁来强制清理连接。

我不知道一种查询现有结果集资源以释放它们的方法;唯一的方法是将这些资源保存在私有数组中。

于 2020-02-22T23:48:40.483 回答
19

用锁杀死连接

Kill 1398

然后检查您是否将自动提交设置为 0

select @@autocommit;

如果是,您可能忘记提交事务。然后另一个连接想对这个表做一些事情,这导致了锁。

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

编辑 对我来说,这个错误就在你的应用程序的某个地方。检查那里,或者如果您没有在应用程序中使用事务,则将自动提交设置为 1 。

ps 也检查这个帖子:

于 2013-11-05T23:47:01.333 回答
0

在现代 MariaDB 中,您可以运行以下命令来保留连接,同时清除阻塞查询

sudo mysql -e "KILL QUERY ID XXXX"

您可以通过运行以下命令获取 QUERY_ID:

SELECT QUERY_ID FROM information_schema.PROCESSLIST WHERE ID = XXXX;

如果您杀死进程 ID 与查询 ID,您正在运行的任何脚本都将失去其连接并可能导致下游问题。

于 2022-01-29T14:33:39.833 回答
0

如果您有 HS 插件并尝试CREATEALTER已经尝试通过 HS 评估的表,您将面临类似的问题,您必须以这种方式重新启动 HS 插件以释放表元数据锁定:

UNINSTALL PLUGIN HANDLERSOCKET;
INSTALL PLUGIN HANDLERSOCKET SONAME 'handlersocket.so';
于 2016-03-02T20:01:54.070 回答