mysql 存储过程 lock table_MYSQL 锁:metadata lock

metadata lock的超时时间是lock_wait_timeout,并不是innodb_lock_wait_timeout

MySQL 5.5.3版本中引入了Metadata lock: DDL语句打破了事务的隔离级别

在5.5.3版本之前,MySQL事务对于表结构元数据(Metadata)的锁定是语句(statement)粒度的

即语句执行完成后,不管事务是否可以完成,其表结构就可以被其他会话更新掉!

引入Metadata lock后,表结构元数据(Metadata)的锁定变成了事务(transaction)粒度的

即只有事务结束时才会释放Metadata lock。

现象:

显式开启事务后start transaction; 该事务内的query语句(包含select)会占用相关表的metadata lock(profile:Opening tables阶段)

导致DDL语句被阻塞,因为获取不到表的metadata lock

MySQL 5.6.6版本后 优化

metadata lock不阻塞DDL语句,但原有session再访问此表时会返回Error信息“Table definition has changed, please retry transaction”

具体案例:

引入Metadata lock之前(5.5.3版本以前)

5.5.3之前的Metadata处理方式,有如下问题:

1)隔离级别会被破坏

例如:理论上REPEATABLE-READ隔离级别下,一个事务内同样的查询能够读到同样的内容,如果没有新记录的话。但是由于没有事务级别的Metadata锁,这个隔离级别下就不能满足这样的约定:

5.1.51版本中运行:

Session1 mysql> select @@tx_isolation;

+—————–+

| @@tx_isolation  |

+—————–+

| REPEATABLE-READ |

+—————–+

1 row in set (0.00 sec)Session1 mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

Session1 mysql> select * from table1 where id=1;

+——+——+——+——-+

| id   | one  | two  | three |

+——+——+——+——-+

|    1 |   34 |   45 |    80 |

+——+——+——+——-+

1 row in set (0.00 sec)

Session2 mysql> alter table table1 add column c1 int;

Query OK, 4 rows affected (0.23 sec)

Records: 4  Duplicates: 0  Warnings: 0

Session1 mysql> select * from table1 where id=1;

Empty set (0.00 sec)

可以看到Session1的事务内部,虽然隔离级别是“可重复读”,但是由于并发的Session2中途成功更改了table1的结构(因为前一个SQL查完table1后就释放了对表结构的约束),两次查询的结果不同!

遗留问题1.为什么第二次查询,记录没有发生变化,但结果集返回空?

2)可能导致复制的失败

上例的影响还比较明显,这种机制还有隐藏的危害,即可能影响复制的成功进行!

我们知道,binlog内操作的记录是基于事务的提交顺序进行的。与上例类似,ALTER语句后执行但可能先提交,这影响到了执行中的其他事务,而那些事务后提交。而SLAVE看来完全是ALTER先执行,这很可能导致被打断的事务在SLAVE上重现时导致失败,要么表结构不对,要么数据导致不一致。

引入Metadata lock之后(5.5.3版本及以后)

而5.5.3中引入了Metadata lock后,由于事务结束后才会释放,避免了这样的问题。还是上例,在5.5.30版本中运行:

Session1 mysql> select @@tx_isolation;

+—————–+

| @@tx_isolation  |

+—————–+

| REPEATABLE-READ |

+—————–+

1 row in set (0.00 sec)Session1 mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

Session1 mysql> select * from table1 where id=1;

+——+——+——+——-+

| id   | one  | two  | three |

+——+——+——+——-+

|    1 |   34 |   45 |    80 |

+——+——+——+——-+

1 row in set (0.00 sec)

Session2 mysql> alter table table1 add column c1 int;

这个语句一直等待。

遗留问题2:为什么没有锁等待超时??

我们再开一个会话查看当前状况:

Session3 mysql>show full processlist;

+——–+———–+———————-+—————+———+——+———————————+————————————–+

| Id     | User      | Host                 | db            | Command | Time | State                           | Info                                 |

+——–+———–+———————-+—————+———+——+———————————+————————————–+

| 163612 | sup       | 192.168.2.213:1844   | dbadb         | Sleep   |   37 |                                 | NULL                                 |

| 163613 | sup       | 192.168.2.213:1883   | dbadb         | Query   |   21 | Waiting for table metadata lock | alter table table1 add column c3 int |

| 163614 | sup       | 192.168.2.213:1884   | dbadb         | Query   |    0 | NULL                            | show full processlist                |

+——–+———–+———————-+—————+———+——+———————————+————————————–+

这里的示例中,我们可以看到ALTER TABLE会应Metadata lock而阻塞。一旦Session 1中的事务提交或者回滚,即释放了table1表的Metadata lock,Session2的操作立即可以执行。

那么究竟是怎样的操作会被Metadata lock影响到呢?我们可以借助SHOW PROFILE看看到底ALTER命令在哪里卡住了:

Session2 mysql> show profile;

+——————————+———-+

| Status                       | Duration |

+——————————+———-+

| starting                     | 0.000093 |

| checking permissions         | 0.000062 |

| checking permissions         | 0.000058 |

| init                         | 0.000058 |

| Opening tables               | 0.000078 |

| System lock                  | 0.000061 |

| setup                        | 0.000077 |

| creating table               | 0.053156 |

| After create                 | 0.000111 |

| copy to tmp table            | 0.000231 |

| rename result table          | 5.780077 |

| end                          | 0.000545 |

| Waiting for query cache lock | 0.000064 |

| end                          | 0.000126 |

| query end                    | 0.000090 |

| closing tables               | 0.000110 |

| freeing items                | 0.000118 |

| logging slow query           | 0.000095 |

| logging slow query           | 0.000125 |

| cleaning up                  | 0.000090 |

+——————————+———-+

20 rows in set (0.02 sec)

可以看到,卡住的那一步是在rename result table,MySQL后台将ALTER保存变成连续操作“创建临时新表->插入老表的数据->临时新表取到老表(RENAME)”,即实际的变化老表的操作在RENAME阶段才发生。而这一步即是被阻塞的那一步。所以可以确认,Metadata lock影响到的是要真实修改表结构的动作。

基于此,我们可以验证ALTER TABLE,RENAME TABLE,DROP TABLE都会被Metadata lock影响,验证步骤与上类似,省略。

值得注意的是,创建一个已存在的表也会被Metadata lock影响。如果在一个事务中用到了表A的Metadata lock(如SELECT了该表)且事务未完成,那么创建同名的表也会被卡住。所参考的文章中提及这一点。有兴趣可参看:讨论页  ,这里不作讨论。

metadata lock的另一个副作用:如上例中,ALTER TABLE应metadata lock被阻塞后,这导致后续其他事务针对该表的SELECT也会被阻塞!即ALTER TABLE的操作会影响到其他SELECT操作。根据上述示例:

Session2 mysql> alter table table1 add column c1 int;

此步一直处于等待状态时,新会话中查询table1。

Session3 mysql> select * from table1 where id=1; #一直等待

Session4 mysql>show full processlist;

+——–+———–+———————-+—————+———+——+———————————+————————————–+

| Id     | User      | Host                 | db            | Command | Time | State                           | Info                                 |

+——–+———–+———————-+—————+———+——+———————————+————————————–+

| 163612 | sup       | 192.168.2.213:1844   | dbadb         | Sleep   |   45 |                                 | NULL                                 |

| 163613 | sup       | 192.168.2.213:1883   | dbadb         | Query   |   35 | Waiting for table metadata lock | alter table table1 add column c1 int |

| 163614 | sup       | 192.168.2.213:1884   | dbadb         | Query   |    0 | NULL                            | show full processlist                |

| 163615 | sup       | 192.168.2.213:2142   | dbadb         | Query   |   16 | Waiting for table metadata lock | select * from table1 where id=1      |

+——–+———–+———————-+—————+———+——+———————————+————————————–+

Session1 mysql> commit;

第一个会话提交后,Session2、Session3也成功执行。看看Session3的select在哪个步骤需要等待:

Session3 mysql> show profile;

+——————————–+———–+

| Status                         | Duration  |

+——————————–+———–+

| starting                       |  0.000018 |

| Waiting for query cache lock   |  0.000003 |

| checking query cache for query |  0.000037 |

| checking permissions           |  0.000007 |

| Opening tables                 | 22.502591 |

| System lock                    |  0.000013 |

| Waiting for query cache lock   |  0.000024 |

| init                           |  0.000022 |

| optimizing                     |  0.000009 |

| statistics                     |  0.000011 |

| preparing                      |  0.000015 |

| executing                      |  0.000003 |

| Sending data                   |  0.000036 |

| end                            |  0.000009 |

| query end                      |  0.000005 |

| closing tables                 |  0.000006 |

| freeing items                  |  0.000007 |

| Waiting for query cache lock   |  0.000003 |

| freeing items                  |  0.000015 |

| Waiting for query cache lock   |  0.000002 |

| freeing items                  |  0.000002 |

| storing result in query cache  |  0.000004 |

| logging slow query             |  0.000002 |

| logging slow query             |  0.000028 |

| cleaning up                    |  0.000003 |

+——————————–+———–+

可以看到SELECT语句首先检查查询缓存,没有命中,然后在Opening tables阶段卡住。

这引出了另一个特点,基于上述场景,如果后续的SELECT能够在查询缓存中命中,那么不会被ALTER TABLE卡住,因为不需要Opening tables操作。命中缓存SELECT的SHOW PROFILE如下:

mysql>  show profile;

+——————————–+———-+

| Status                         | Duration |

+——————————–+———-+

| starting                       | 0.000017 |

| Waiting for query cache lock   | 0.000004 |

| checking query cache for query | 0.000006 |

| checking privileges on cached  | 0.000004 |

| checking permissions           | 0.000007 |

| sending cached result to clien | 0.000011 |

| logging slow query             | 0.000002 |

| cleaning up                    | 0.000003 |

+——————————–+———-+

8 rows in set (0.00 sec)

遗留问题1.为什么第二次查询,记录没有发生变化,但结果集返回空?

这个问题在这个页面进行了讨论,确认原因是:

MySQL中ALTER操作实际后台是“创建新表”->”拷贝老表数据”->”RENAME新表成老表”。InnoDB引擎针对客户端的REPEATABLE READ隔离级别,采用的是基于多版本(Multi-version)功能的”一致性读”,即在事务中第一个查询时,获取的是当时版本的表的快照,而ALTER操作后实际是创建的新表,这个新表对于之前的快照而言是没有数据的,所以第二次查询新表基于快照的数据,返回空。在MySQL 5.6.6版本后,这样的第二个查询会返回Error信息“Table definition has changed, please retry transaction”。详细请看这里。

遗留问题2.为什么没有锁等待超时??

我们知道InnoDB行锁的等待时间超过innodb_lock_wait_timeout就会报超时错误。这里的metadata lock一直等待着,为什么没有超时呢?其实metadata lock的超时设置是另一个系统变量lock_wait_timeout。

lock_wait_timeout控制着所有涉及到metadata lock的操作,包括DML and DDL,以及在表、视图、存储过程、存储函数上的操作,以及LOCK TABLES、FLUSH TABLES WITH READ LOCK、HANDLER等。

lock_wait_timeout的默认设置时一年,可以动态设置,根据实际场景进行调整。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL 8中的“waiting for table metadata lock”错误通常表示在运行某个查询或DML语句时,另一个进程已经占据了需要访问的数据表或索引,导致当前进程无法获取到对应的元数据,从而无法继续执行。 元数据MySQL中的一种排他,用于保护数据表的元数据信息(例如表结构、索引信息等)。当一个进程需要查询或修改数据表的元数据时,它会请求获得对应的元数据,以保证自己能够独占这些元数据信息的访问。但是,如果另一个进程已经占据了相应的,当前进程就需要等待的释放才能继续执行,这就是“waiting for table metadata lock”的错误原因。 为了避免这个问题,我们可以尝试采取以下措施: 1. 使用更高的隔离级别(如repeatable read或serializable)来保证访问数据表时的可重复读性,从而减少对元数据的频繁访问和冲突的机会。 2. 减少对数据表结构的频繁修改操作,避免在高并发环境中进行DDL语句(如ALTER TABLE)的执行。 3. 尽量避免将多个操作合并成一条长SQL语句,尽可能将多个小操作拆分成多条短SQL语句,从而减少对数据表的定时间。 4. 使用更高效的数据库架构设计(如分库分表、索引优化等),将数据的读写操作分配到更多的物理资源中,从而减少对单个数据表的定时间。 在实际进行MySQL数据库开发或运维时,我们需要注意以上几点,以避免“waiting for table metadata lock”错误的发生。如果不幸遇到此类问题,我们需要通过查看进程列表、慢查询日志、错误日志或排查sql执行计划等方式,以找出造成冲突的根本原因。针对不同的错误原因,我们可以采取不同的解决方法,从而有效避免MySQL 8中的元数据问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值