【问题标题】:Mysql 8 metadata locks on foreign key tables during alter更改期间外键表上的 Mysql 8 元数据锁定
【发布时间】:2023-12-24 05:17:01
【问题描述】:

当我执行alter table 命令时,我会遇到很多死锁。其中命令是 waiting for metadata lock 在不属于 alter table 语句的表上。这些表是通过外键链接的,但我们不会更改外键。

有没有办法在不让mysql崩溃的情况下运行alter tables?

小例子

给定这两个表表有外键关系。

create table parent(id int primary key);
create table child(
    id int primary key,
    `parent_id` int,
    constraint `parent_id_fk` foreign key (`parent_id`) references `parent` (`id`)
);

当连接读取一些数据时,它会获取元数据锁。

start transaction read only;
select * from child;
-- commit later

然后一个单独的连接尝试在父表上运行一个 alter 语句。即使我们没有接触孩子或 id,这也需要对孩子进行元数据锁定。

alter table parent add column x int null;

我们可以看到这些锁

mysql> select * from performance_schema.metadata_locks ;
+-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+--------------------+-----------------+----------------+
| OBJECT_TYPE | OBJECT_SCHEMA      | OBJECT_NAME    | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE           | LOCK_DURATION | LOCK_STATUS | SOURCE             | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+--------------------+-----------------+----------------+
| TABLE       | performance_schema | metadata_locks | NULL        |        47604062188640 | SHARED_READ         | TRANSACTION   | GRANTED     | sql_parse.cc:6213  |           24981 |             15 |
| GLOBAL      | NULL               | NULL           | NULL        |        47604063432464 | INTENTION_EXCLUSIVE | STATEMENT     | GRANTED     | sql_base.cc:5412   |           23547 |            607 |
| BACKUP LOCK | NULL               | NULL           | NULL        |        47604007764640 | INTENTION_EXCLUSIVE | TRANSACTION   | GRANTED     | sql_base.cc:5419   |           23547 |            607 |
| SCHEMA      | test               | NULL           | NULL        |        47604063432224 | INTENTION_EXCLUSIVE | TRANSACTION   | GRANTED     | sql_base.cc:5399   |           23547 |            607 |
| TABLE       | test               | parent         | NULL        |        47604068904032 | SHARED_UPGRADABLE   | TRANSACTION   | GRANTED     | sql_parse.cc:6213  |           23547 |            607 |
| TABLESPACE  | NULL               | test/parent    | NULL        |        47604068895072 | INTENTION_EXCLUSIVE | TRANSACTION   | GRANTED     | lock.cc:793        |           23547 |            607 |
| SCHEMA      | test               | NULL           | NULL        |        47604068902992 | INTENTION_EXCLUSIVE | STATEMENT     | GRANTED     | sql_table.cc:1117  |           23547 |            607 |
| TABLE       | test               | child          | NULL        |        47604007764800 | SHARED_UPGRADABLE   | STATEMENT     | GRANTED     | sql_table.cc:1109  |           23547 |            607 |
| TABLE       | test               | #sql-1f53_5ad9 | NULL        |        47604063517984 | EXCLUSIVE           | STATEMENT     | GRANTED     | sql_table.cc:16153 |           23547 |            607 |
| TABLE       | test               | child          | NULL        |        47604063429264 | EXCLUSIVE           | STATEMENT     | PENDING     | sql_table.cc:1109  |           23547 |            608 |
| TABLE       | test               | child          | NULL        |        47604010642320 | SHARED_READ         | TRANSACTION   | GRANTED     | sql_parse.cc:6213  |           24466 |            120 |

生产中的死锁

在我们有更多表和外键的生产系统中,我们看到死锁,其中alter table 为表A 获取锁但无法获取表B。表B 被需要表A 的线程锁定。 对我们来说,它有点竞争​​条件,但由于 mysql 需要锁定 所有 的链接表,而不仅仅是一个正在改变的表,这加剧了这种情况。

这似乎是 mysql 8 的新行为。我希望我没有升级。我们在带有 innodb 表的 mysql 8.0.20 上。

有没有办法在不锁定数据库的情况下运行这些更改?理想情况下,我想保留我的外键,我不想使用像 Percona 这样的迁移管理器。我的桌子甚至没有那么大。

谢谢

【问题讨论】:

    标签: mysql mysql-8.0 table-locking


    【解决方案1】:

    要使您的 ALTER TABLE 语句不加锁,或者如果在不加锁的情况下无法执行操作时返回错误,请执行以下操作:

    ALTER TABLE ..., LOCK=NONE;
    

    【讨论】:

    • 嗨,这在这种情况下似乎不起作用。它似乎忽略了它。我不确定原因可能是它忽略了元数据锁?
    • 呵呵;用列列表替换* 有帮助吗?我没有其他想法
    • 是的,也许切换到更好的数据库,PostgreSQL 有工具可以帮助 :-) wiki.postgresql.org/wiki/… 。锁用于更改,但有据可查,并且有相当简单的解决方法