————————————————-
在MySQL5.1及之前的版本中,如果有未提交的事务trx,当执行DROP/RENAME/ALTER TABLE RENAME操作时,不会被其他事务阻塞住。这会导致如下问题(MySQL bug#989)
未提交的事务,但SQL已经完成(binlog也准备好了),表schema发生更改,在commit的时候不会被察觉到。
在binlog里是以事务提交顺序记录的,DDL隐式提交,因此在备库先执行DDL,后执行事务trx,由于trx作用的表已经发生了改变,因此trx会执行失败。
在DDL时的主库DML压力越大,这个问题触发的可能性就越高
session1,set autocommit=0,对表b执行一条DML
root@xxx 11:48:28>set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
root@xxx 11:48:35>insert into b values (NULL,4);
Query OK, 1 row affected (0.00 sec)
session2,执行rename table a to tmp_b
root@xxx 11:48:23>rename table b to tmp_b;
Query OK, 0 rows affected (0.01 sec)
root@xxx 11:49:00>show binlog events;
+——————+—–+—-———+———–+——–—–+—————————————+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+——————+—–+—-———+———–+——–—–+—————————————+
| mysql-bin.000001 | 4 | Format_desc | 12 | 106 | Server ver: 5.1.48-log, Binlog ver: 4 |
| mysql-bin.000001 | 106 | Query | 12 | 191 | use `xxx`; rename table b to tmp_b |
| mysql-bin.000001 | 191 | Query | 12 | 258 | BEGIN |
| mysql-bin.000001 | 258 | Table_map | 12 | 298 | table_id: 195 (xxx.b) |
| mysql-bin.000001 | 298 | Write_rows | 12 | 336 | table_id: 195 flags: STMT_END_F |
| mysql-bin.000001 | 336 | Xid | 12 | 363 | COMMIT /* xid=737 */ |
+——————+—–+—-———+———–+——–—–+—————————————+
显然当这样的Binlog同步到备库的话,必然会导致复制中断。
> drop table t1 / alter table t1 rename to t2
rename table t1 to t2这样的DDL不适用于上述方法。
在5.5引入了MDL(meta data lock)锁来解决在这个问题,至于5.1,官方已经明确回复不会FIX,太伤感了。。。
我们来看看MDL是如何解决这个问题的,还是以rename为例吧
—————————————————————————————
我们知道,在5.5之前,当事务的一条语句执行完后,就会释放占有的数据字典锁,MDL的作用就是延迟这种锁的释放。
对于非事务表或者运行在autocommit=1时没有什么影响。
MDL的相关API和定义都在文件mdl.cc和mdl.h中
1. MDL_INTENTION_EXCLUSIVE
意向排他mdl锁,只用于范围锁(MDL_scoped_lock)。拥有该锁的可以获得单个对象升级的排他锁
该类型锁与其他IX锁相容,但和范围X和S锁不相容。
更高优先级意味着和其他共享锁不一样,它可以忽略等待的排他锁请求。主要用于只需要访问metadata而非数据时。例如,填充一个i_s表时。
共享MDL读锁当试图从表中读取数据时加上该锁。持有该锁时,可以读取表的元数据和表的数据
主要适用于insert/delete/update,select…for update。不会作用于lock table write或者DDL。
可升级的共享MDL锁,会阻塞所有的DML,但不阻塞读,可以升级为x MDL锁
在ALTER TABLE的第一阶段(表之间拷贝数据),允许并发的对表做select,但不允许更新
7.MDL_SHARED_NO_READ_WRITE
可升级的MDL锁,允许其他连接获取metadata信息,但不允许访问数据。
该锁会阻塞所有试图读/写表的数据,但允许information_schema及show操作
用于LOCK TABLES WRITE这样的语句。除了S和SH锁外,不与其他的锁相容。
持有该锁时,可以修改表的Metadata和数据,当持有该锁时,不能持有其他类型的任何mdl锁。用于CREATE/DROP/RENAME语句以及其他DDL语句的某些部分。
MDL_STATEMENT:在SQL完成或事务结束时释放
MDL_EXPLICIT:显式的加锁,在事务或SQL完成后依旧持续,需要显式的调用 MDL_context::release_lock()来释放锁。
一个MDL锁的lock Key由以下几个部分组成:
<mdl_namespace>+<database name>+<table name>
mdl_namespace是枚举类型,用于区分对象的类型
206 enum enum_mdl_namespace { GLOBAL=0,207 SCHEMA, 208 TABLE,209 FUNCTION,210 PROCEDURE,211 TRIGGER,212 EVENT,213 COMMIT,214 /* This should be the last ! */215 NAMESPACE_END };
############################################
MDL_wait_for_graph_visitor:An abstract class for inspection of a connected subgraph of the wait-for graph.
MDL_wait_for_subgraph:Abstract class representing an edge in the waiters graph to be traversed by deadlock detection algorithm.
class MDL_ticket : public MDL_wait_for_subgraph
在同一个对象上的多个共享锁由一个ticket来表示,其他类型的锁不是这样。
—可外部访问的(Externally accessible)
MDL_savepoint:用于事务中存在savepoint时,作回滚用。
MDL_context:Context of the owner of metadata locks. I.e. each server connection has such a context.
################################################
举一个简单的例子来阐述mdl锁吧,还是以rename为例
系统启动时,会在init_server_components里mdl_init
初始化mdl相关变量,例如m_locks hash结构体,用于存储所有的MDL锁。
create table t1 (a int auto_increment primary key , b int );
> insert into t1 values (NULL,2);
MDL_context::acquire_lock(sql_base.cc:2920)
open_table_get_mdl_lock(sql_base.cc:2929)
MDL_context::acquire_lock
在本例中table->mdl_request.type为MDL_SHARED_WRITE
session2: rename table t1 to t2;
session2需要请求表上的MDL_EXCLUSIVE锁,这是强度最大的锁,与其他所有的MDL锁类型都不兼容,由于之前的INSERT操作的session持有MDL_SHARED_WRITE锁,因此这里需要等待
lock_table_names ——-搜集需要的锁
->MDL_context::acquire_locks
->MDL_context::acquire_lock —等待
->MDL_context::release_transactional_locks 释放持有的MDL锁
MDL子系统的代码很复杂,以上也只是一个非常简单的例子,从如下的PDF中,你可以获得更详细的关于MDL如何设计的信息。