`
BucketLi
  • 浏览: 190073 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
博客专栏
5a76a659-f8e6-3bf3-b39a-8ae8f7a0f9d9
Percolator与分布...
浏览量:5550
社区版块
存档分类
最新评论

Innodb中的锁机制(转)

 
阅读更多
原文地址:http://www.mysqlops.com/2012/05/19/locks_in_innodb.html

基本概念
- 锁的基本类型
  A. 共享锁(Shared Lock) 也叫读锁.
  B. 排他锁(Exclusive Lock) 也叫写锁.

- S、X锁的兼容性矩阵
    S  X
  S +  –
  X -  -
  + 代表兼容, -代表不兼容

- 锁的粒度
  A. 表锁(Table Lock)
  B. 行锁(Row Lock)

- 意向锁(Intention Lock)
  因为表锁覆盖了行锁的数据,所以表锁和行锁也会产生冲突。如:
  A. trx1 BEGIN
  B. trx1 给 T1 加X锁,修改表结构。
  C. trx2 BEGIN
  D. trx2 给 T1 的一行记录加S或X锁(事务被阻塞,等待加锁成功)。
  trx1要操作整个表,锁住了整个表。那么trx2就不能再对T1的单条记录加X或S锁,去读取或修这条记录。
  为了方便检测表级锁和行级锁之间的冲突,就引入了意向锁。
  A. 意向锁分为意向读锁(IS)和意向写锁(IX)。
  B. 意向锁是表级锁,但是却表示事务正在读或写某一行记录,而不是整个表。
     所以意向锁之间不会产生冲突,真正的冲在加行锁时检查。
  C. 在给一行记录加锁前,首先要给该表加意向锁。也就是要同时加表意向锁和行锁。
  采用了意向锁后,上面的例子就变成了:
  A. trx1 BEGIN
  B. trx1 给 T1 加X锁,修改表结构。
  C. trx2 BEGIN
  D. trx2 给 T1 加IX锁(事务被阻塞,等待加锁成功,然后在给某一行记录加X锁。)

- 表锁的兼容性矩阵
     IS IX S  X
  IS +  +  +  –
  IX +  +  -  -
  S  +  -  +  -
  X  -  -  -  -
  + 代表兼容, -代表不兼容
  A. 意向锁之间不会冲突, 因为意向锁仅仅代表要对某行记录进行操作。在加行锁时,会判断是否冲突。
01 – 行锁
  直观的理解,行锁就是要锁住一行记录,阻止其他事务操作该行记录。这里有一个隐含的逻辑:
  A. 插入操作永远不会被阻止,因为插入操作不会操作一条存在的记录(这里不考虑Insert
     duplicate的处理)。这个逻辑是对的吗? 这和用户的使用情况相关,有些情况下是用户能接受的,有些情况下是用户不能接受的。

- 幻读(Phantom Read)
  如果不阻止INSERT操作,就会产生幻读.MySQL手册中有幻读的介绍.
  A. MVCC 可以避免幻读.但是MVCC只对SELECT语句有效,对于
     SELECT … [LOCK IN SHARE MODE | FOR UPDATE], UPDATE, DELETE语句无效。
  B. 为了能够通过锁避免幻读,采用了next-key的机制。next-key通过锁住2个记录之间的间隙,来阻止INSERT操作。

- 行锁的模式
  行锁S、X锁上做了一些精确的细分,在代码中称作Precise Mode。这些精确的模式,
  使的锁的粒度更细小。可以减少冲突。
  A. 间隙锁(Gap Lock),只锁间隙。
  B. 记录锁(Record Lock) 只锁记录。
  C. Next-Key Lock(代码中称为Ordinary Lock),同时锁住记录和间隙.
  D. 插入意图锁(Insert Intention Lock),插入时使用的锁。在代码中,插入意图锁,
     实际上是GAP锁上加了一个LOCK_INSERT_INTENTION的标记.
  MySQL手册对这些模式有详细的介绍.

- 行锁模式的兼容性矩阵
     G  I  R  N (已经存在的锁,包括等待的锁)
  G  +  +  +  +
  I  -  +  +  -
  R  +  +  -  -
  N  +  +  -  -
  + 代表兼容, -代表不兼容. I代表插入意图锁,
  G代表Gap锁,I代表插入意图锁,R代表记录锁,N代表Next-Key锁.
  S锁和S锁是完全兼容的,因此在判别兼容性时不需要对比精确模式。
  精确模式的检测,用在S、X和X、X之间。
  这个矩阵是从lock0lock.c:lock_rec_has_to_wait()的代码推出来的。从这个矩阵可以看到几个特点:
  A. INSERT操作之间不会有冲突。
  B. GAP,Next-Key会阻止Insert。
  C. GAP和Record,Next-Key不会冲突
  D. Record和Record、Next-Key之间相互冲突。
  E. 已有的Insert锁不阻止任何准备加的锁。
  同时也有几个疑问:
  A. 为什么插入意图锁不阻止间隙锁?在特定的情况下会导致INSERT操作被无限期延迟。
  B. 如果不阻止任何锁,这个锁还有必要存在吗?
     - 目前看到的作用是,通过加锁的方式来唤醒等待线程。
     - 但这并不意味着,被唤醒后可以直接做插入操作了。需要再次判断是否有锁冲突。
  C. GAP+LOCK_INSERT_INTENTION标记的方式,能否直接变成INSERT_INTENTION锁?
     目前还在看。

- B+Tree 行锁
  InnoDB的行锁并不是简单的数据行锁的概念。而是指每个B+Tree上的行锁,也可以理解为每个
  Index上的行锁。因此操作一行记录时,有可能会加多个行锁在不同的B+Tree上。如:
   CREATE TABLE t1(c1 INT KEY, c2 int, c3 int, INDEX(c2));
   INSERT INTO t1 VALUES(1, 1, 1), (3, 3, 3)
   UPDATE t1 c3 = 10 WHERE c2 <= 2
  UPDATE语句会同时在Secondary Index和Clustered Index上加锁。

- 行锁模式的使用
  行锁的这些模式都在什么情况下使用呢? MySQL手册有详细的介绍。
  A. Next-Key 使用在被WHERE条件用到的索引上(准确的说是用来做Search的索引上)。
     上面的例子中,Index(c2)上使用 Next-Key Lock.
  B. Record Lock使用在没有被WHERE条件使用的索引上。上面的例子中,簇索引上使用Record Lock.因此上面的UPDATE语句会同时在加Index(c2)的键1上加Next-Key,在主键1上加record锁。当另一个session并发插入(2,5,2),(3,5,2)时可以成功,但是(2,2,2)时会被阻塞。

Next-Key And Record
     测试时发现,SELECT…[FOR UPDATE |LOCKIN SHARE MODE]可能会导致全部记录被锁住。
     当表很小时,SELECT会采用全表扫描的方法。在使用这种方法时,遍历了所有的数据,
     因此所有数据都被锁住了。尽管对不符合条件的记录调用ha_innobase::unlock_row(),
     但是在Repeatable Read级别时不会被释放。也许该算一个Bug.
  C. A、B同时适用于SELECT…[FOR UPDATE | LOCK IN SHARE MODE], UPDATE、DELETE语句。
  D. GAP锁显然也是使用在WHERE条件使用的索引上。和Next-Key不同的是,GAP锁只加在上边界(第一
     个大于符合条件的记录)上。而Next-Key加在所有符合条件的记录上。上面例子中的条件c2=2的记录,
     需要在c2=3上加一个GAP锁。
     ? 正向查询时,InnoDB中实际上在边界上加的是Next-Key锁。 这可能是受实现的限制。
     目前使用GAP情况有:
     – Supremum记录上始终是一个GAP锁
     – 反向查询(ORDER BY DESC)时.
     – 等值匹配一个确切的键值时,对下一条记录加GAP锁。
     – 等值匹配一个确切的键值的前缀时,对下一条记录加GAP锁。。
  E. INSERT时,通常不加锁。只有当其他事务在插入点加了Gap或Next-key锁需要等待时,
     才会创建一个插入意图锁。这个锁是在waiting状态。

- 隔离级别对Next-Key锁的影响
  A. Read Uncommitted和Read Committed时,不需要在间隙上加锁,Nexk-Key变成Record锁。
  B. Repeatable Reads 和 Serializable时,通常情况下使用Next-key锁。
     有2中情况不需要对间隙加锁:
     – 查询一个唯一的值,如 WHERE c1 = 1, c1 是主键或唯一键,并且查询结果中不含NULL字段。
     – 当innodb_locks_unsafe_for_binlog被开启。这里还是有一些值得思考的问题:
     ? 从这个情况来看,UPDATE,DELETE时加间隙锁完全是为了防止Master和Slave数据不一致。
       那么不使用binlog时就没有必要对DELETE, UPDATE加间隙锁。
     ? Row Format Binlog时,不加间隙锁是否会引起Master, Slave不一至。
     ? 即便设置了innodb_locks_unsafe_for_binlog,SELECT…[]是否可以不加间隙锁。
  判断加什么锁的主要工作在row0sel.c:row_search_for_mysql()中。

– 延迟加锁机制
  如果一个表有很多的索引,那么操作一个记录时,岂不是要加很多锁到不同的B-Tree上吗?
  先来看一个事务的状态信息:
   CREATE TABLE t1(c1 INT KEY, c2 INT);
   BEGIN;
   INSERT INTO t1 VALUES(1, 1);
   INSERT INTO t1 VALUES(2, 2);
   SHOW ENGINE INNODB STATUS;
  状态信息:
   LIST OF TRANSACTIONS FOR EACH SESSION:
   ---TRANSACTION 501, ACTIVE 0 sec
   1 lock struct(s), heap size 376, 0 row lock(s), undo log entries 2

– 隐式锁
  Lock 是一种悲观的顺序化机制。它假设很可能发生冲突,因此在操作数据时,就加锁。
  如果冲突的可能性很小,多数的锁都是不必要的。
  Innodb 实现了一个延迟加锁的机制,来减少加锁的数量,在代码中称为隐式锁(Implicit Lock)。
  隐式锁中有个重要的元素,事务ID(trx_id).隐式锁的逻辑过程如下:
  A. InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于簇索引的B+Tree中。
  B. 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚).
     如果是活动的事务,首先将隐式锁转换为显式锁(就是为该事务添加一个锁)。
  C. 检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E。
  D. 等待加锁成功,被唤醒,或者超时。
  E. 写数据,并将自己的trx_id写入trx_id字段。Page Lock可以保证操作的正确性。
  相关代码:
  A. lock_rec_convert_impl_to_expl()将隐式锁转换成显示锁。
  B. 加锁和测试行锁冲突都用lock_rec_lock(),它的第一个参数表示是否是隐式锁。所以要特别
     注意这个参数。如果为TRUE,在没有冲突时并不会加锁。
  C. 测试行锁的冲突的具体内容在lock_rec_has_wait()
  D. 创建waiting锁是lock_rec_enqueue_waiting()
  E. 创建行锁是lock_rec_add_to_queue()

– 隐式锁的特点
  A. 只有在很可能发生冲突时才加锁,减少了锁的数量。
  B. 隐式锁是针对被修改的B+Tree记录,因此都是Record类型的锁。不可能是Gap或Next-Key类型。

– 隐式锁的使用
  A. INSERT操作只加隐式锁,不需要显示加锁。
  B. UPDATE,DELETE在查询时,直接对查询用的Index和主键使用显示锁,其他索引上使用隐式锁。
     理论上说,可以对主键使用隐式锁的。提前使用显示锁应该是为了减少死锁的可能性。
     INSERT,UPDATE,DELETE对B+Tree们的操作都是从主键的B+Tree开始,因此对主键加锁可以有效的阻止死锁。

– Secondary Index上的隐式锁
  前边说了, trx_id只存在于主键上,那么辅助索引上如何来实现隐式索引呢?
  显然是要通过辅助索引中的主键值,在主键B+Tree上进行二次查找。这个开销是很大的。
  InnoDB对这个过程有一个优化:
  A. 每个页上有一个MAX_TRX_ID,每次修改辅助索引的记录时,都会更新这个最大事务ID。
  B. 当判断是否要将隐式锁变为显式锁时,先将页面的max_trx_id和事务列表的最小trx_id
     比较。如果max_trx_id比事务列表的最小trx_id还小,那么就不需要转换为显示锁了。
  代码在lock_sec_rec_some_has_impl_off_kernel()中
  /* Some transaction may have an implicit x-lock on the record only
  if the max trx id for the page >= min trx id for the trx list, or
  database recovery is running. We do not write the changes of a page
  max trx id to the log, and therefore during recovery, this value
  for a page may be incorrect. */
  if (page_get_max_trx_id(page) < trx_list_get_min_trx_id()
      && !recv_recovery_is_on()) {
     return(NULL);
  }


– 锁的存放
  A. table->locks 存放一个表的所有表级锁。
  B. lock_sys->rec_hash存放所有表的行锁。Hash值根据(spaceid, pageno)来计算。
  C. trx->trx_locks存放事务的所有锁,包括表级锁和行级锁。一个事务的所有锁,在事务
     结束时,一起释放。代码在lock_release_off_kernel().如果有等待的锁可以被授权,
     则会将等待的锁,转变为被授权的锁,并唤醒相应的事务。

– 行锁的唯一识别
  第一印象想到的是,用每行记录的键值来做行锁的唯一识别.但是键值占用空间比较大。
  InnoDB使用Page NO.+Heap NO.来做行锁的唯一识别。我们可以将Heap no.理解为页面上的
  一个自增数值。每条物理记录在被创建时,都会分配一个唯一的heap no.
  A. 键值可以理解为一个逻辑值,page no. + heap no. 是物理的。
  B. 物理的虽然占用空间小,但是处理要复杂一些。如:在分裂一个B+Tree页面时,一半的记录
     要移到新的页面中,因此要对存在的锁进行迁移。
     锁移动的d函数有:lock_move_reorganize_page(), lock_move_rec_list_start(),
     lock_move_rec_list_end().
     在删除和插入数据时,也要进行GAP锁的继承。lock_rec_inherit_to_gap()
     lock_rec_inherit_to_gap_if_gap_lock().

– 死锁(Deadlock)
  A. 超时机制。当要加的锁和其他锁冲突时,添加一个waiting锁,并且返回DB_LOCK_WAIT错误。
     row_mysql_handle_error调用srv_suspend_mysql_thread来挂起一个线程。
  B. 死锁检测检测机制。每当创建waiting锁,都要调用lock_deadlock_occurs()进行死锁的检测。
     死锁检测方法是Waits-For Graph.在lock_deadlock_recursive()中实现。
     当发现死锁后要选择其中的一个事务,将其回滚,来解除死锁。选择哪一个事务回滚能?
     – 如果一个事务修改了non-transactional表(如MyISAM表,修改不能回滚),另一个表没有。
       则没有修改non-transactional的会被回滚。
     – 如果2个事务都修改了non-transactional表或者都没有。则比较2个事务修改的记录数和加
       的锁数量。总和小的事务会被回滚。trx_weight_ge()实现这个逻辑。
分享到:
评论

相关推荐

    InnoDB锁机制学习笔记

    MySQL的引擎简介,InnoDB的锁机制与事务隔离级别

    MySQL InnoDB中的锁机制深入讲解

    主要给大家介绍了关于MySQL InnoDB中锁机制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用MySQL具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    解析数据库锁协议和InnoDB的锁机制(全面解析行级锁、表级锁、排他锁、共享锁、悲观锁、乐观锁等常用锁)

    数据库通过锁以及锁协议来进行并发控制,解决并发事务带来的问题,本篇博文主要是解析数据库的锁协议和Mysql的默认存储引擎InnoDB的锁机制。 如果对事务隔离级别以及并发事务带来的问题不熟悉可以翻阅我的另外一篇...

    mysql索引和锁机制简介

    关于mysql中的innodb存储引擎的索引机制简介,详细介绍了锁机制

    mysql内核 innodb存储引擎

    接着以InnoDB的内部实现为切入点,逐一详细讲解了InnoDB存储引擎内部的各个功能模块,包括InnoDB存储引擎的体系结构、内存中的数据结构、基于InnoDB存储引擎的表和页的物理存储、索引与算法、文件、锁、事务、备份,...

    MyISAM InnoDB 区别

    如果是Innodb,恐怕不可能有这么快速度,别和我说让Innodb定期用导出xxx.sql机制备份,因为我平台上最小的一个数据库实例的数据量基本都是几十G大小。  4、从我接触的应用逻辑来说,select count(*) 和order by 是...

    详谈innodb的锁(record,gap,Next-Key lock)

    Record lock单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql...

    innodb如何巧妙的实现事务隔离级别详解

    之前的文章mysql锁机制详解中我们详细讲解了innodb的锁机制,锁机制是用来保证在并发情况下数据的准确性,而要保证数据准确通常需要事务的支持,而mysql存储引擎innodb是通过锁机制来巧妙地实现事务的隔离特性中的4...

    MySQL内核:InnoDB存储引擎 卷1.pdf

    机工畅销图书作者亲自执笔,在以往出版的两本InnoDB介绍性图书的基础之上,更深入地介绍InnoDB存储引擎的内核,例如latch、B+树索引、事务、锁等,从源代码的角度深度解析了InnoDB的体系结构、实现原理、工作机制,...

    MySQL数据库InnoDB存储引擎中的锁机制

    锁就是其中的一种机制。我们用商场的试衣间来做一个比喻。试衣间供许多消费者使用。因此可能有多个消费者同时要试衣服。为了避免冲突,试衣间的门上装了锁。试衣服的人在里边锁住,其他人就不能从外边打开了。只有...

    MySQL锁机制与用法分析

    本文实例讲述了MySQL锁机制与用法。分享给大家供大家参考,具体如下: MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁;BDB存储引擎采用的是...

    MYSQL 解锁与锁表介绍

    相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level ...

    你真的懂Mysql的锁吗?详谈Myql的锁机制

    要说锁,应该追溯到操作系统中的多线程原理,锁机制在其中发挥着必不可少的作用,先抛出锁的定义 锁是计算机协调多个进程或线程并发访问某一资源的机制 在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用...

    mysql中的锁机制深入讲解

    本文主要论述关于mysql锁机制,mysql版本为5.7,引擎为innodb,由于实际中关于innodb锁相关的知识及加锁方式很多,所以没有那么多精力罗列所有场景下的加锁过程并加以分析,仅根据现在了解的知识,结合官方文档,...

    Innodb与Myisam引擎的区别与应用场景

    Innodb与Myisam引擎的区别与应用场景 . 区别: (1)事务处理: MyISAM是非事务安全型的,而InnoDB是事务安全型的(支持事务处理等高级处理);...(2)锁机制不同: MyISAM是表级锁,而InnoDB是行级锁;

    MySQL的Innodb中的事务隔离级别和锁的关系

    这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么。  #一次封锁or两段锁?  因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,是在...

    InnoDB中的MVCC

    MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。 不仅是MySQL,包括Oracle、 PostgreSQL等其他数据库系统也都实现了 MVCC,但各自的...

    innoDB 索引结构详解

    锁粒度 行锁 表锁 并发性 高并发 低并发 构成结构和缓存机制 数据和索引文件都存在在.Idb文件里,并且都缓存在内存里。 数据文件都扩展名.MYD(MYData) 索引文件都扩展名.MYI(MYIndex) 只缓存索引文件,不缓存...

    MySQL中Innodb的事务隔离级别和锁的关系的讲解教程

    这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么。 一次封锁or两段锁? 因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法...

Global site tag (gtag.js) - Google Analytics