MySQL Lock机制

INDEX:

  • MySQL事务隔离级别
  • MVCC
  • MySQL Lock类型
  • MySQL MDL

CONTENT:

1. MySQL事务隔离级别

  • Read Uncommit

    RU:

    允许脏读, 也就是说本事务中可能读到其他事务中未提交的修改数据.

  • Read Commit

    RC

    只能读到已经提交的数据. Oracle等多数数据库默认都是该级别(不可重复读).

  • Repeatable Read

    RR

    可重复读, 在同一个事务内的查询都是事务开始时刻的一致性数据, InnoDB默认的事务隔离级别. 该隔离级别消除了不可重复读, 但是还存在幻想读.

  • Serializable

    S

    完全串行化的读, 每次读都需要获取表级别的共享锁, 读写相互组赛.

      transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}

2. MVCC

MVCC: Multiversion concurrency control

基于锁的并发控制机制, 悲观机制;

基于版本的并发控制机制, 乐观机制;

读不阻塞写, 写也不阻塞读, 等到提交的时候才检验是否有冲突. 由于没有锁, 所以读写不会相互阻塞, 从而大大提升了并发性能.

3. MySQL Lock类型

根据锁的类型分:

  • 共享锁
  • 排他锁
  • 意向共享锁
  • 意向排他锁

根据锁的粒度分:

  • 表锁
  • 行锁

而在InnoDB存储引擎层实现了行锁(Record Lock), gap lock, next-key lock.

查看lock的信息

  • show variables like 'tx_isolation';

  • show engine innodb status\G

  • SELECT

    r.trx_id waiting_trx_id, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_query blocking_query, b.trx_mysql_thread_id blocking_thread, b.trx_started, b.trx_wait_started

    FROM

    information_schema.innodb_lock_waits w

    INNER JOIN information_schema.innodb_trx b

    ON b.trx_id = w.blocking_trx_id

    INNER JOIN information_schema.innodb_trx r

    ON r.trx_id = w.requesting_trx_id;

MDL

先熟悉一下MDL_key的state name:

  • global read lock
  • commit lock
  • schema metadata lock
  • table metadata lock
  • stored function metadata lock
  • stored procedure metadata lock
  • trigger metadata lock
  • event metadata lock

从MySQL对锁持有的时间分:

  • Statement

    语句执行期间获取的lock, 语句执行结束时自动释放的.

  • Transaction

    在一个事务中, 此事务所设计到的所有表均要获取MDL, 一直到事务commit/rollback释放.

    两阶段加锁协议:

    事务执行分为两个阶段,

    • 第一个阶段获得封锁;
    • 第二个阶段释放封锁;
  • Explicit

    需要MDL_context::release_lock(), 譬如: lock table; flush tables with read lock;

从MySQL的锁粒度分:

  • scope锁(MDL_scoped_lock(const MDL_key *key_arg))

    名称 意义
    IS 意向共享锁
    IX 意向排他锁
    S 共享锁
    X 排他锁

    优先级: X ->S ->IX (IS和其他类型的锁都是兼容的)

    • 兼容性

        individual locks转换为scoped lock:
      
        ----------------+-------------+
      
        Type of request | Correspond. |
      for indiv. lock | scoped lock |
      ----------------+-------------+
      S, SH, SR, SW | IS |
      SNW, SNRW, X | IX |
      SNW, SNRW -> X | IX (*) | request与granted之间的兼容矩阵: | Type of active |
      Request | scoped lock |
      type | IS(**) IX S X |
      ---------+------------------+
      IS | + + + + |
      IX | + + - - |
      S | + - + - |
      X | + - - - | request与waiting兼容矩阵: | Pending |
      Request | scoped lock |
      type | IS(**) IX S X |
      ---------+-----------------+
      IS | + + + + |
      IX | + + - - |
      S | + + + - |
      X | + + + + | Here: "+" -- means that request can be satisfied
      "-" -- means that request can't be satisfied and should wait (*) Since for upgradable locks we always take intention exclusive scoped
      lock at the same time when obtaining the shared lock, there is no
      need to obtain such lock during the upgrade itself.
      (**) Since intention shared scoped locks are compatible with all other
      type of locks we don't even have any accounting for them.
  • object锁(MDL_object_lock(const MDL_key *key_arg))

    名称 意义
    MDL_INTENTION_EXCLUSIVE 仅适用scoped locks, 可升级IX->X, 和其他IX兼容; 但是和scoped S and X不兼容.
    MDL_SHARED 共享锁. 访问字典对象(table metadata), 不能访问数据
    MDL_SHARED_HIGH_PRIO 高优先级shared mdl, 不像shared lock那样, 在申请时会忽略X lock的等待; 用于访问metadata(no date), 填充INDORMATION_SCHEMA表. 兼容SNRW
    MDL_SHARED_READ 共享读锁. 能读table metadata, 也可读表数据(如select), 譬如: SELECTs, subqueries, and LOCK TABLE ... READ
    MDL_SHARED_WRITE 共享写锁. 读取metadata, modify/read table data, (INSERT, UPDATE, DELETE) statements,SELECT ... FOR UPDATE. but not LOCK TABLE ... WRITE or DDL)
    MDL_SHARED_UPGRADABLE alter table第一阶段获取. 读取metadata, modify/read table data(升级到SNW, 获取row_level lock), 可升级SHU->SNW/X.
    MDL_SHARED_NO_WRITE alter table第一阶段获取; 可以并发read table, 但不可以update; 可升级SNW->X.
    MDL_SHARED_NO_READ_WRITE 读取表metadata, modify/read table data. 但是阻止对表的读写操作. 用于LOCK TABLES WRITE statement. 可升级SNRW->X, 除S/SH外, 不兼容任何mdl.
    MDL_EXCLUSIVE 最高级别MDL锁, 通常用于Drop/Create/Rename等操作. 也用于其它对象创建或者删除时, 譬如: create trigger等
    • 兼容性

        request与granted兼容矩阵:(0为不可能情况)
      
          Request  |  Granted requests for lock   |
      type | S SH SR SW SNW SNRW X |
      ----------+------------------------------+
      S | + + + + + + - |
      SH | + + + + + + - |
      SR | + + + + + - - |
      SW | + + + + - - - |
      SNW | + + + - - - - |
      SNRW | + + - - - - - |
      X | - - - - - - - |
      SNW -> X | - - - 0 0 0 0 |
      SNRW -> X | - - 0 0 0 0 0 | request与waiting兼容矩阵: Request | Pending requests for lock |
      type | S SH SR SW SNW SNRW X |
      ----------+-----------------------------+
      S | + + + + + + - |
      SH | + + + + + + + |
      SR | + + + + + - - |
      SW | + + + + - - - |
      SNW | + + + + + + - |
      SNRW | + + + + + + - |
      X | + + + + + + + |
      SNW -> X | + + + + + + + |
      SNRW -> X | + + + + + + + | Here: "+" -- means that request can be satisfied
      "-" -- means that request can't be satisfied and should wait
      "0" -- means impossible situation which will trigger assert @note In cases then current context already has "stronger" type
      of lock on the object it will be automatically granted
      thanks to usage of the MDL_context::find_ticket() method.
  • 数据结构

    和锁相关的数据结构:

    • MDL_context: 字典锁上下文. 包含一个事物所有的字典锁请求.
    • MDL_request: 字典锁请求. 包含对某个对象的某种锁的请求.
    • MDL_ticket: 字典锁排队. MDL_request就是为了获取一个ticket.
    • MDL_lock: 锁资源. 一个对象全局唯一, 可以允许多个可以并发的事物同时获得.

    源码文件主要是sql/mdl.cc

  • 加锁/解锁分析

    • SELECT加锁调用:

        mysql_execute_command --> execute_sqlcom_select --> open_normal_and_derived_tables-->
      open_tables --> open_and_process_table --> open_table--> MDL_context::acquire_lock

      看看acquire lock的流程:

      • 检查session是否持有该object的share lock? 如果是, 则grant.

          if ((ticket= find_ticket(mdl_request, &found_duration)))
        {
        mdl_request->ticket= ticket;
        }
      • 如果找不到, 在MDL_map中找到MDL_lock, grant lock.

          if (!(lock= mdl_locks.find_or_insert(key)))
        { }
        ticket->m_lock= 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加入到lock的waiting队列中

          lock= ticket->m_lock;
        lock->m_waiting.add_ticket(ticket);
      • 死锁检测

          find_deadlock();
        
          void MDL_context::find_deadlock()
        {
        while (1)
        {
        if (! visit_subgraph(&dvisitor))
        {
        breadk;
        } victim= dvisitor.get_victim();
        (void) victim->m_wait.set_status(MDL_wait::VICTIM);
        victim->unlock_deadlock_victim(); if (victim == this)
        break;
        //发现deadlock, 函数退出
        } }
    • SELECT解锁调用:

        mysql_execute_command --> MDL_context::release_transactional_locks -->
      MDL_context::release_locks_stored_before --> MDL_context::release_lock

      看看release lock的流程:

      • 通过ticket信息找到MDL_lock

          DBUG_ASSERT(this == ticket->get_ctx());
        mysql_mutex_assert_not_owner(&LOCK_open);
      • 将ticket从MDL_lock的granted list中remove

          lock->remove_ticket(&MDL_lock::m_granted, ticket);
        void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket)
        {
        m_list.remove(ticket);
        }
      • 检查MDL_lock的waiting list和granted list是否为空? 如果是, 则将MDL_lock从MDL_map中remove

          void MDL_map::remove(MDL_lock *lock)				{
        mysql_mutex_lock(&m_mutex);
        my_hash_delete(&m_locks, (uchar*) lock); if (ref_usage == ref_release)
        {
        MDL_lock::destroy(lock);
        }
        } void MDL_context::destroy()
        {
        DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty() &&
        m_tickets[MDL_TRANSACTION].is_empty() &&
        m_tickets[MDL_EXPLICIT].is_empty());
        mysql_prlock_destroy(&m_LOCK_waiting_for); }
      • 如果非空, 遍历waiting list, 尝试加锁, 并从waiting list中remove, 加入到granted list中, 唤醒对应session

  • SELECT与DDL(alter table)问题

    经常会看到大SQL查询堵住alter table, alter table又会堵住后面的一堆SQL ......

    看到一堆wait for table metadata lock, 醉了T.T

    下来分析根本原因:

    • 先看看alter的MDL流程:

      • opening tables阶段

        MDL_INTENTION_EXCLUSIVE

        MDL_SHARED_UPGRADABLE, 可升级到MDL_SHARED_NO_WRITE

      • copy data阶段

        copy data to tmp table...

      • del原表, 将tmp表rename回去

        MDL_SHARED_NO_WRITE升级到MDL_EXCLUSIVE

      • 事务commit, release MDL

        release MDL_INTENTION_EXCLUSIVE

        release MDL_EXCLUSIVE

    • select是需要获取表的MDL_SHARED_READ

      在alter table中的rename之前表被持有MDL_SHARED_NO_WRITE, 此时申请MDL_SHARED_READ是可以成功的, 兼容.

    这里关键的步骤是rename环节, SNW->X; 如果此时表在被select(即被持有MDL_SHARED_READ); 从兼容矩阵可以看出SNW升级到X是不能与SR兼容的, 所以这个alter table的rename环节就必须等待select结束!

  • 几种典型的加锁/释放锁的流程

    • select statement

      • opening tables阶段, 加共享读锁

        • 加MDL_INTENTION_EXCLUSIVE锁
        • 加MDL_SHARED_READ锁
      • commit阶段, 释放MDL锁

        • 释放MDL_INTENTION_EXCLUSIVE锁
        • 释放MDL_SHARED_READ锁
    • DML statement

      • opening tables阶段, 加共享写锁

        • 加MDL_INTENTION_EXCLUSIVE锁
        • 加MDL_SHARED_WRITE锁
      • commit阶段, 释放MDL锁

        • 释放MDL_INTENTION_EXCLUSIVE锁
        • 释放MDL_SHARED_WRITE锁
    • DDL(alter table) statement

      • opening tables阶段, 加共享写锁

        • 加MDL_INTENTION_EXCLUSIVE锁
        • 加MDL_SHARED_UPGRADABLE锁, 升级到MDL_SHARED_NO_WRITE
      • 读取数据, copy data, 流程:

        • create {tmp} table, 定义tmp表结构
        • copy data from old table to tmp(new) table
      • tmp表替换老表(rename)

        • 将MDL_SHARED_NO_WRITE升级到MDL_EXCLUSIVE
      • commit阶段, 释放MDL锁

        • 释放MDL_INTENTION_EXCLUSIVE锁
        • 释放MDL_EXCLUSIVE锁

[MySQL] lock知识梳理的更多相关文章

  1. MySQL 基础知识梳理

    MySQL 的安装方式有多种,但是对于不同场景,会有最适合该场景的 MySQL 安装方式,下面就介绍一下 MySQL 常见的安装方法,包括 rpm 安装,yum 安装,通用二进制安装以及源码编译安装, ...

  2. MySQL 基础知识梳理学习(七)----sync_binlog

    一般在生产环境中,很少用MySQL单实例来支撑业务,大部分的MySQL应用都是采用搭建集群的方法.搭建MySQL集群,可以进行数据库层面的读写分离.负载均衡或数据备份.基于MySQL原生的Replic ...

  3. MySQL 基础知识梳理学习(五)----详解MySQL两次写的设计及实现

    一 . 两次写提出的背景或要解决的问题 两次写(InnoDB Double Write)是Innodb中很独特的一个功能点.因为Innodb中的日志是逻辑的,所谓逻辑就是比如插入一条记录时,它可能会在 ...

  4. MySQL 基础知识梳理学习(四)----GTID

    在日常运维中,GTID带来的最方便的作用就是搭建和维护主从复制.GTID的主从模式代替了MySQL早期版本中利用二进制日志文件的名称和日志位置的做法,使用GTID使操作和维护都变得更加简洁和可高. 1 ...

  5. MySQL 基础知识梳理学习(一)----系统数据库

    information_schema 此数据库是MySQL数据库自带的,主要存储数据库的元数据,保存了关于MySQL服务器维护的所有其他数据库的信息,如数据库名.数据库表.表列的数据类型及访问权限等. ...

  6. MySQL 基础知识梳理学习(六)----锁

    1.什么是锁: 对共享资源进行并发访问控制,提供数据的完整性和一致性. 2.锁的区别: 类型 lock latch 对象 事务 线程 保护 数据库内容 内存数据结构 持续时间 整个事务过程 临界资源 ...

  7. MySQL 基础知识梳理学习(五)----半同步复制

    1.半同步复制的特征 (1)从库会在连接到主库时告诉主库,它是不是配置了半同步. (2)如果半同步复制在主库端是开启了的,并且至少有一个半同步复制的从节点,那么此时主库的事务线程在提交时会被阻塞并等待 ...

  8. MySQL 基础知识梳理学习(三)----InnoDB日志相关的几个要点

    1.InnoDB的特点 :(1)Fully ACID (InnoDB默认的Repeat Read隔离级别支持):(2)Row-level Locking(支持行锁):(3)Multi-version ...

  9. MySQL 基础知识梳理学习(二)----记录在页面层级的组织管理

    1.InnoDB的数据存储结构 InnoDB中数据是通过段.簇.页面构成的. (1)段是表空间文件中的主要组织结构,它是一个逻辑概念,用来管理物理文件,是构成索引.表.回滚段的基本元素.创建一个索引( ...

随机推荐

  1. python字符编码小结

    首先简要说一下各种字符编码: 1. ASCII 计算机只认识0101,但如何让计算机认识人类语言?将每个字母和符号给予固定的编号,然后将这个编号转换成二进制,计算机就可以正确识别这些字母与符号,同时计 ...

  2. 机顶盒demux的工作原理

    在机顶盒中demux部分相对来说是比较复杂的部分,对于机顶盒软件开发的新手来说通常在这里会遇到一些困难,今天特意研究了一下驱动层代码,有一点自己的理解,因此写下来记录一下学习过程. 机顶盒中数据是如何 ...

  3. B1019 数字黑洞 (20分)

    B1019 数字黑洞 (20分) 给定任一个各位数字不完全相同的 4 位正整数,如果我们先把 4 个数字按非递增排序,再按非递减排序,然后用第 1 个数字减第 2 个数字,将得到一个新的数字.一直重复 ...

  4. 笔记-python-tutorial-5.data structure

    笔记-python-tutorial-5.data structure 1.      data structure 1.1.    list operation list.append(x) #尾部 ...

  5. UVa 1649 Binomial coefficients 数学

    题意: \(C(n, k) = m(2 \leq m \leq 10^{15})\),给出\(m\)求所有可能的\(n\)和\(k\). 分析: 设\(minK = min(k, n - k)\),容 ...

  6. itchat 总结(转)

    python实现微信接口(itchat) 安装 sudo pip install itchat 登录 itchat.auto_login() 这种方法将会通过微信扫描二维码登录,但是这种登录的方式确实 ...

  7. redis 之相关命令

    为什么缓存数据库更要首选redis?如何使用redis? 一.使用缓存数据库为什么首选用redis? 我们都知道,把一些热数据存到缓存中可以极大的提高速度,那么问题来了,是用Redis好还是Memca ...

  8. JQuery easyUi datagrid 中 自定义editor作为列表操作按钮列

    转自   http://blog.csdn.net/tianlincao/article/details/7494467 前言 JQuery easyUi datagrid 中 使用datagrid生 ...

  9. 根据窗口尺寸onresize判断窗口的大小

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  10. 【bzoj1475】方格取数 网络流最小割

    题目描述 在一个n*n的方格里,每个格子里都有一个正整数.从中取出若干数,使得任意两个取出的数所在格子没有公共边,且取出的数的总和尽量大. 输入 第一行一个数n:(n<=30) 接下来n行每行n ...