本文共 6932 字,大约阅读时间需要 23 分钟。
MySQL 从5.5.3版本,对Metadata lock进行了调整,主要是MDL锁持有的周期从语句变成了事务, 其原因主要是解决两个问题:
问题1: 破坏事务隔离级别
在repeatable read的隔离级别下,多次的select语句执行过程中,会因为其它session的DDL语句,而导致select语句执行的结果不相同,破坏了RR的隔离级别。问题2: 破坏binlog的顺序
在对表的DML过程中,会因为其它session的DDL语句,导致binlog里的event顺序在备库执行的结果和主库不一致。从MySQL 5.5.3开始,MDL锁的持有周期变成了事务,解决了上面提到的两个问题,但在autocommit=off的情况下,也大大增加了阻塞的可能性。DBA对于阻塞的case,处理起来又比较麻烦,原因就是MDL锁的阻塞情况没有暴露明确的信息。
从MySQL 5.7.6开始,可以通过performance schema来查询MDL锁的持有情况。
在开始介绍5.7的跟踪Metadata lock之前, 小编还想讨论一下前面提到的这两个问题,在Oracle数据库中是如何处理的。
首先,Oracle只实现了两种隔离级别,即read committed和serializable,我们来看下serializable级别下,怎么来处理问题1:
先看如下的case:
session 1: session 2:-- create table t1(id number);-- insert into t1 values(1);-- commit;SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; --TEST/TEST@ORCL>select * from t1; -- ID---------- 11 row selected.-- alter table t1 add col number;TEST/TEST@ORCL>select * from t1; -- ID COL---------- ---------- 1-- alter table t1 add col1 number default 10;TEST/TEST@ORCL>select * from t1; -- ID COL COL1---------- ---------- ---------- 1
可以看到,虽然session是serializable隔离级别,但并没有产生阻塞的情况,Oracle保证了session1的多次select查询的返回结果是一样的,
但t1表数据字典的变化是马上可见的,这个也是符合serializable的要求的,因为隔离级别只定义了数据的可见性,而没有定义数据字典的可见性。那MySQL能否不要MDL锁,来达到这样的效果?
答案是否定的,因为Oracle是堆表,alter的操作只更改了数据字典,数据记录没有发生变化,纵使加了default值,也是在原记录上进行的update,完全可以使用scn号来构建一致性读版本,这样就不会产生阻塞。
而MySQL是IOT表,alter的过程进行了表重建,无法完成read view的构建。那我们再来看问题2,Oracle的处理方式:
对于redo日志,Oracle的处理方式和InnoDB的处理方式一致,也就是当使用redo的时候,日志的写入并不和事务的提交与否有必然的关系,也不用和提交的顺序保持一致。这一点就和binlog区别开来,也就是物理日志是可以避免使用逻辑日志(binlog)带来的问题。
MySQL如果要避免这两个问题,而不引入Metadata lock,可以有以下两个思路:
这两种都会对现有的MySQL架构带来调整,仅供参考。
下面我们回来看下对5.7 MDL的tracing。
首先,打开metadata locks的tracing功能。
mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation';Query OK, 0 rows affected (0.00 sec)Rows matched: 1 Changed: 0 Warnings: 0mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';Query OK, 0 rows affected (0.00 sec)Rows matched: 1 Changed: 0 Warnings: 0
打开两个session,一个select,一个truncate。因为MDL锁的情况,select会阻塞truncate的操作。
session 1: 操作如下:
mysql> set session autocommit=0;Query OK, 0 rows affected (0.00 sec)mysql> select @@autocommit, @@tx_isolation;+--------------+----------------+| @@autocommit | @@tx_isolation |+--------------+----------------+| 0 | READ-COMMITTED |+--------------+----------------+1 row in set (0.00 sec)mysql> select * from t limit 1;+----+------+| id | val |+----+------+| 1 | 1 |+----+------+1 row in set (0.00 sec)
session 2: 操作如下:
mysql> truncate table t;
结果看到的就是session2被阻塞, 接下来check一下performance schema的信息:
mysql> select * from performance_schema.metadata_locks\G*************************** 1. row ***************************OBJECT_TYPE: TABLEOBJECT_SCHEMA: testOBJECT_NAME: tOBJECT_INSTANCE_BEGIN: 140450128308592LOCK_TYPE: SHARED_READLOCK_DURATION: TRANSACTIONLOCK_STATUS: GRANTEDSOURCE: sql_parse.cc:5585OWNER_THREAD_ID: 27OWNER_EVENT_ID: 17*************************** 2. row ***************************OBJECT_TYPE: GLOBALOBJECT_SCHEMA: NULLOBJECT_NAME: NULLOBJECT_INSTANCE_BEGIN: 140450195436144LOCK_TYPE: INTENTION_EXCLUSIVELOCK_DURATION: STATEMENTLOCK_STATUS: GRANTEDSOURCE: sql_base.cc:5224OWNER_THREAD_ID: 30OWNER_EVENT_ID: 8*************************** 3. row ***************************OBJECT_TYPE: SCHEMAOBJECT_SCHEMA: testOBJECT_NAME: NULLOBJECT_INSTANCE_BEGIN: 140450195434272LOCK_TYPE: INTENTION_EXCLUSIVELOCK_DURATION: TRANSACTIONLOCK_STATUS: GRANTEDSOURCE: sql_base.cc:5209OWNER_THREAD_ID: 30OWNER_EVENT_ID: 8*************************** 4. row ***************************OBJECT_TYPE: TABLEOBJECT_SCHEMA: testOBJECT_NAME: tOBJECT_INSTANCE_BEGIN: 140450195434368LOCK_TYPE: EXCLUSIVELOCK_DURATION: TRANSACTIONLOCK_STATUS: PENDINGSOURCE: sql_parse.cc:5585OWNER_THREAD_ID: 30OWNER_EVENT_ID: 8*************************** 5. row ***************************OBJECT_TYPE: TABLEOBJECT_SCHEMA: performance_schemaOBJECT_NAME: metadata_locksOBJECT_INSTANCE_BEGIN: 140450128262384LOCK_TYPE: SHARED_READLOCK_DURATION: TRANSACTIONLOCK_STATUS: GRANTEDSOURCE: sql_parse.cc:5585OWNER_THREAD_ID: 27OWNER_EVENT_ID: 185 rows in set (0.00 sec)
如上所示,在t表上,持有一个SHARE_READ lock,而且还有一个EXCULSIVE lock请求是pending状态,也就是我们被阻塞的session 2。
在5.7之前,我们可以通过show processlist,来查看MDL阻塞的情况,但无法获取session 1的信息:
mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO FROM performance_schema.metadata_locks INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID WHERE PROCESSLIST_ID <> CONNECTION_ID();+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_STATUS | THREAD_ID | PROCESSLIST_ID | PROCESSLIST_INFO |+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+| GLOBAL | NULL | NULL | INTENTION_EXCLUSIVE | GRANTED | 30 | 8 | truncate table t || SCHEMA | test | NULL | INTENTION_EXCLUSIVE | GRANTED | 30 | 8 | truncate table t || TABLE | test | t | EXCLUSIVE | PENDING | 30 | 8 | truncate table t |+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+3 rows in set (0.00 sec)mysql> show processlist;+----+------+-----------+------+---------+------+---------------------------------+------------------+| Id | User | Host | db | Command | Time | State | Info |+----+------+-----------+------+---------+------+---------------------------------+------------------+| 5 | root | localhost | test | Query | 0 | starting | show processlist || 8 | root | localhost | test | Query | 50 | Waiting for table metadata lock | truncate table t |+----+------+-----------+------+---------+------+---------------------------------+------------------+2 rows in set (0.00 sec)
接下来当事务提交了后,释放MDL锁再查询,就看不到MDL锁的信息了。
mysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO FROM performance_schema.metadata_locks INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID WHERE PROCESSLIST_ID <> CONNECTION_ID();Empty set (0.01 sec)mysql> select * from t;Empty set (0.00 sec)
MySQL 5.7可以通过performance schema来检索MDL锁阻塞情况,方便DBA来诊断问题。
转载地址:http://tieym.baihongyu.com/