MySQL为了保护数据字典元数据,使用了metadata lock,即MDL锁,保证在并发的情况下,结构变更的一致性。

MDL锁的加锁模式和源码上的组织上和上一篇blog中MySQL表锁的实现方式一致,都采用了【mutex+condition+queue】来实现并发,阻塞,唤醒的控制。

下面就来看看MDL锁:

1. 重要的数据结构:

1. MDL_map

mdl_map使用hash表,保存了MySQL所有的mdl_lock,全局共享,使用MDL_KEY作为key来表,key=【db_name+table_name】唯一定位一个表。

2. mdl_context

mdl_context在MySQL为每一个connection创建thd时,初始化一个mdl上下文,保存了当前session请求的mdl信息。

3. MDL_lock

mdl_lock表示系统的一个mdl锁,所有的mdl request都请求对应的mdl_lock,这个mdl_lock结构保存了两个queue,一个是grant_queue表示拿到lock的请求队列。

一个是wait_queue表示请求这个mdl_lock的阻塞队列。

4. MDL_wait

mdl_wait包装了一个mutex和一个condition,提供了所有的加锁,wait,notify操作。

5. MDL_request

在open table的时候,会init一个request,包含了请求的enum_mdl_type,enum_mdl_duration,MDL_ticket,MDL_key。

下面再看看三个重要的枚举类型:

enum enum_mdl_namespace { GLOBAL=0,

SCHEMA,

TABLE,

FUNCTION,

PROCEDURE,

TRIGGER,

EVENT,

COMMIT,

/* This should be the last ! */

NAMESPACE_END };

enum enum_mdl_duration {

/**

Locks with statement duration are automatically released at the end

of statement or transaction.

*/

MDL_STATEMENT= 0,

/**

Locks with transaction duration are automatically released at the end

of transaction.

*/

MDL_TRANSACTION,

/**

Locks with explicit duration survive the end of statement and transaction.

They have to be released explicitly by calling MDL_context::release_lock().

*/

MDL_EXPLICIT,

/* This should be the last ! */

MDL_DURATION_END };

enum enum_mdl_type {

MDL_INTENTION_EXCLUSIVE= 0,

MDL_SHARED,

MDL_SHARED_HIGH_PRIO,

MDL_SHARED_READ,

MDL_SHARED_WRITE,

MDL_SHARED_NO_WRITE,

MDL_SHARED_NO_READ_WRITE,

MDL_EXCLUSIVE,

MDL_TYPE_END};

首先:enum_mdl_namespace 表示mdl_request的作用域,比如alter table操作,需要获取TABLE作用域。

然后:enum_mdl_duration 表示mdl_request的持久类型,比如alter table操作,类型是MDL_STATEMENT,即语句结束,就释放mdl锁。又比如autocommit=0;select 操作,类型是MDL_TRANSACTION,必须在显示的commit,才释放mdl锁。

最后:enum_mdl_type 表示mdl_request的lock类型,根据这个枚举类型,来判断是否兼容和互斥。

2. 测试

下面根据一个测试,看一下加锁,释放,阻塞的过程,已经主要的函数调用栈:

session1:            session2:

set autocommit=0;        alter table pp add name varchar(100):

select * from pp;

2.1 创建connection过程中,初始化mdl_context.

函数调用:

handle_connections_sockets

MDL_context::init: 每一个connection对应一个mdl_context

2.2 初始化mdl_request

函数调用:

parse_sql

st_select_lex::add_table_to_list

MDL_request::init

说明: 在session1的过程中,创建的mdl_request:

mdl_namespace=MDL_key::TABLE,

db_arg=0x8c7047c8 "xpchild",

name_arg=0x8c7047d0 "pp",

mdl_type_arg=MDL_SHARED_READ,

mdl_duration_arg=MDL_TRANSACTION

2.3 加锁

acquire_lock:

if (lock->can_grant_lock(mdl_request->type, this))

{

lock->m_granted.add_ticket(ticket);

mysql_prlock_unlock(&lock->m_rwlock);

m_tickets[mdl_request->duration].push_front(ticket);

mdl_request->ticket= ticket;

}

说明:首先进行兼容性判断,如果兼容,那么就把ticket加入到队列中,加锁成功。

函数调用栈

open_and_lock_tables

open_table

1. 排他锁使用

lock_table_names

MDL_context::acquire_locks

2. 共享锁使用

open_table_get_mdl_lock

MDL_context::try_acquire_lock

2.4 阻塞

下面进入session2. 因为session1拿到了pp表的share读锁,但session2的alter操作的mdl_request类型是:MDL_INTENTION_EXCLUSIVE,兼容性判断是互斥,所以ddl被阻塞。

while (!m_wait_status && !thd_killed(thd) &&

wait_result != ETIMEDOUT && wait_result != ETIME)

{

wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,abs_timeout);

}

说明:上面的这段代码,session2进入阻塞状态,等待超时或者mdl_wait中的条件变量。

2.5 唤醒

session1进行提交动作,commit。 然后session1 release mdl_lock,最后wake up session2.  session 2完成alte操作。

MDL_context::release_lock();

lock->remove_ticket();

reschedule_waiters();

while ((ticket= it++))

{

if (can_grant_lock(ticket->get_type(), ticket->get_ctx()))

{

if (! ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED))

MDL_wait::set_status();

mysql_cond_signal(&m_COND_wait_status);

说明: commit操作,释放session 1持有的mdl事务锁,然后遍历wait队列,判断兼容性测试,最后wakeup session2.

总结: 根据上面的测试,我们看到,mdl的机制和表锁的机制基本一致性,但从上面的测试和源码的设计上,也看到MySQL表锁,mdl锁令人蛋疼的地方。

3. 蛋疼的锁

下面简单介绍下MySQL锁令人蛋疼的两个地方:

1. 事务开始begin transaction的位置

MySQL的设计:在设置的autocommit=0;read_commited的时候,无论session的第一条语句是select还是dml,都开始一个事务,然后直到commit,所持有的MDL锁也一直维持到commit结束。

Oracle的设计:在session的第一条更新语句发起时,才创建transaction,在读多的系统上,减少了阻塞的发生可能性。特别是在开发人员发起select语句时,认为没有更新,就不再commit。但在MySQL上,发起select语句,而忘记commit,是非常危险的。

2. ddl语句阻塞

MySQL的设计:ddl语句发起时,如果无法获取排他锁,那么ddl将进入阻塞状态,但由于是queue的设计,就阻塞了后续所有的dml和selec操作,在高并发系统上,可能会引起雪崩。

Oracle的设计:在oracle 11g之前,ddl语句是fast fail的,不进入阻塞状态,所以繁忙的表进行ddl操作时,经常遇到的错误:ORA-00054: resource busy。但在11g之后虽然可以进行阻塞,并提供了ddl_time_out这样的参数进行控制,但在高并发的系统上,运维的操作依然不采用,而是fast fail。

后话:

这里可以参照oracle的设计进行改良,ddl语句阻塞相对改源码来说,比较简单。而事务开始的位置,牵涉到mvcc和事务隔离级别,改动会比较大。

mysql mdl锁_MySQL锁系列3 MDL锁相关推荐

  1. MySQL mdl导入_MySQL源码学习——MDL字典锁

    什么是MDL MDL,Meta Data lock,元数据锁,一般称为字典锁.字典锁与数据锁相对应.字典锁是为了保护数据对象被改变,一般是一些DDL会对字典对象改变,如两个TX,TX1先查询表,然后T ...

  2. mysql设置乐观锁_mysql数据库怎么设置乐观锁

    乐观锁与悲观锁不同的是,它是一种逻辑上的锁,而不需要数据库提供锁机制来支持当数据很重要, 回滚或重试一次需要很大的开销时,需要保证操作的ACID性质, 此时应该采用悲观锁而当数据对即时的一致性要求不高 ...

  3. mysql常见死锁_MySQL死锁系列-常见加锁场景分析

    如下图所示,数据库的隔离等级,SQL 语句和当前数据库数据会共同影响该条 SQL 执行时数据库生成的锁模式,锁类型和锁数量. 下面,我们会首先讲解一下隔离等级.不同 SQL 语句 和 当前数据库数据对 ...

  4. mysql where从句_MySQL死锁系列-常见加锁场景分析

    如下图所示,数据库的隔离等级,SQL 语句和当前数据库数据会共同影响该条 SQL 执行时数据库生成的锁模式,锁类型和锁数量. 下面,我们会首先讲解一下隔离等级.不同 SQL 语句 和 当前数据库数据对 ...

  5. mysql死锁影响_MySQL死锁系列-常见加锁场景分析

    各位看官内容喜欢的话,动动手指点个 ,点个关注呗!!谢谢支持! 如下图所示,数据库的隔离等级,SQL 语句和当前数据库数据会共同影响该条 SQL 执行时数据库生成的锁模式,锁类型和锁数量. 下面,我们 ...

  6. mysql ddl 锁_MySQL Online DDL导致全局锁表案例分析

    MySQL Online DDL导致全局锁表案例分析 我这边遇到了什么问题? 线上给某个表执行新增索引SQL, 然后整个数据CPU打到100%, 连接数暴增到极限, 最后导致所有访问数据库的应用都奔溃 ...

  7. mysql innodb 间隙锁_MySQL中InnoDB的间隙锁问题

    在为一个客户排除死锁问题时我遇到了一个有趣的包括InnoDB间隙锁的情形.对于一个WHERE子句不匹配任何行的非插入的写操作中,我预期事务应该不会有锁,但我错了.让我们看一下这张表及示例UPDATE. ...

  8. mysql myisam表_mysql优化之MyISAM表锁

    概述 MyISAM存储引擎只支持表锁,mysql的表锁有两种模式:读锁和写锁. 他们的兼容关系是(对myisam的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写操作*)和(对myisa ...

  9. mysql版本号锁_MySQL使用版本号实现乐观锁

    乐观锁适用于读多写少的应用场景 乐观锁Version图示 Project Directory Maven Dependency 1 <?xml version="1.0" e ...

  10. mysql事务隔离级别与锁_mysql事务隔离级别与锁

    2.隔离级别实现 上一节介绍了ANSI定义的3种异象,及根据禁止异象的个数而定义的事务隔离级别.因为不存在严格.严谨的"官方"定义,各主流 2.1 Lock-based 隔离级别实 ...

最新文章

  1. 【OpenCV】5种图像滤波辨析:方框、均值、高斯、中值、双边
  2. Hadoop记录-JMX参数
  3. 如何用python打印田字格_如何用 3D 打印一双顶级跑鞋回形针
  4. cad中等线体_如何撰写人们会实际阅读的中等故事
  5. [ES6] 细化ES6之 -- Promise对象
  6. 【超参数寻优】交叉验证(Cross Validation)超参数寻优的python实现:单一参数寻优
  7. db2 replace函数的用法_C++常用函数整理
  8. 【POJ 3666】Making the Grade【线性DP】
  9. 自己编写的C语言实时时钟代码
  10. 7代cpu能装虚拟xp系统吗_【精选】减少DCS系统故障的实例与措施
  11. FIT2CLOUD获网宿科技战略投资 深度聚焦混合云管理价值交付
  12. 360浏览器显示没有网络连接到服务器地址,Win10 360浏览器提示网络连接错误错误代码102如何解决...
  13. Linux搭建Redis集群(搭建集群必看)
  14. 计算机语言s是什么,什么是语义学(Semantics)?
  15. 在linux平台上如何修改hostid
  16. 消防Linux软件图形显示系统,消防控制室图形显示装置
  17. c语言中local status6,2016年12月英语六级听力真题及答案:第2套
  18. ESLint中no-param-reassign错误
  19. 名帖14 吴让之 篆书《吴均帖》
  20. 从数据治理到数据应用,制造业企业如何突破数字化转型困境丨行业方案

热门文章

  1. esp32学习笔记(4)——adc
  2. 实车、台架功能测试介绍
  3. rapidSVN : Error while performing action: ra_serf: The server sent a truncated HTTP response body.
  4. 个人陈述怎么写计算机专业自招,自主招生个人陈述范文【推荐】
  5. docker 安装 onlyoffice
  6. poj 2942 Knights of the Round Table(双连通分量+tarjan+二分图判定)
  7. 黄海导航软件测试,测试软件平台环境一览
  8. vivado中设置多核编译
  9. svg图形计算、矩阵函数计算、图形点位绝对坐标计算
  10. 本月(2019年8月)算法工程师一二线城市工资,杭州,广州,宁波,合肥半年涨幅在500元以上