zoukankan      html  css  js  c++  java
  • MySQL:Innodb引擎锁总结

    1.锁的目的是什么?
    innodb中利用mvcc(多版本并发控制),可以在不加锁的情况下提高并发访问下系统的吞吐量,但有些场景下并发访问必须要在锁的保护下进行,比如并发的更新。

    2.锁的分类
    一般锁是指lock,innodb中还有不常用的一种轻量级锁latch,latch中不存在死锁检测机制,适合加锁时间较短的场景。
    按兼容性分类,分为S 共享锁和X 排他锁。S锁之间是兼容的,但X锁不与其他锁兼容。
    按粒度分类,分为表级锁、页锁(innodb中好像没有?)、行级锁。innodb支持多粒度锁,它允许行级锁与表级锁共存。

    3.表锁
    mysql的MyISAM使用的是表锁,而innodb有表锁和行锁。
    innodb表级锁包括自增锁、意向锁
    自增锁 AUTO-INC Locks,用于AUTO_INCREMENT的自增主键。在Innodb存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器。当对表进行插入操作时,会依据自增长计数器的计数值加一赋予自增长列.为了提升插入的性能,锁不是事务完成后才释放,而是完成自增长值插入的sql语句后立即释放。

    意向锁 Intention Locks,表示事务有意向对表中的某些行加锁,分为IS 意向共享锁和IX 意向排他锁。IS表示有意向对表中的某些行加共享锁,IX表示有意向对表中的某些行加排他锁。意向锁是由引擎自己维护的,用户无法手动操作,在给数据行加共享/排他锁之前,Innodb会先给对应的表加意向锁。
    意向锁之间是相互兼容的,IX与IX之间也相互兼容。另外,意向锁也不会与行级的共享/排他锁互斥
    意向锁的作用是什么? 意向锁会与表级的共享/排他锁互斥,这里的表级锁指普通的表级锁。例如,事务A执行 SELECT * FROM USERS WHERE ID=9 FOR UPDATE, 此时USERS表有两把锁,USERS表上的IX锁和ID=9的数据行上的排他锁。在事务A未commit的情况下,事务B执行LOCK TABLES USERS READ,此时事务B检测到事务A持有表的IX锁,就可以得知A必然持有该表中某些行的排他锁,因此B对USERS的加锁请求就会被阻塞,就无需检测表中每一行的数据是否存在排他锁。

    4.行级锁
    分为Record Lock,Gap Lock和Next-Key Lock。这里锁都是针对索引,通过索引来实现行锁,而不是通过锁住记录。数据库操作使用主键索引时,会锁住主键索引;使用非主键索引时,会先锁住非主键索引,再锁住主键索引,这样就有可能导致死锁(A先锁住非主键索引再锁住主键索引,而B先锁住主键索引)。
    Record Lock,是单个行记录上的锁。
    Gap Lock,间隙锁,锁住一个范围,但是不包含记录本身。例如事务A执行SELECT * FROM USERS WHERE ID>5 AND ID<8 FOR UPDATE,此时就会锁住(5,8)的区间,事务B尝试插入ID=6的操作就会被阻塞。间隙锁之间互不冲突,它的唯一作用是防止其他事务的插入,因此加间隙S锁和间隙X锁没有区别。RR隔离级别下特有。
    Next-Key Lock,Record Lock+Gap Lock,锁住记录本身,并且锁定一个范围。它的主要目的是,保证Repeatable Read(RR)的事务隔离级别下不出现Phantom Problem幻读问题。innodb默认的事务隔离级别是Repeatable Read,在RR中innodb行锁默认使用Next-key Lock,只有当查询的索引时唯一索引时,innodb会对next-key lock进行优化,将其降级为Record Lock,即仅锁住单条索引本身而不是范围。当查询的索引为辅助索引时,innodb会使用next-key lock加锁。

    5.MVCC
    多版本并发控制,不加锁的情况下支持并发查询提高并发度。在Read Committed和Repeatable Read隔离级别下有不同的表现。Innodb中每一行有三个隐藏列,DATA_TRX_ID表示最近修改该行数据的事务id,DATA_ROLL_PTR表示指向该行undolog段段指针,DELETED_BIT标记该列是否被删除,mvcc就是通过这三个隐藏列实现。
    Read Committed隔离级别下,每次查询得到的是当前已经提交的数据。Repeatable Read级别下,查询得到的是事务开始前的数据。
    怎么实现的? 核心内容:事务版本号,表的隐藏列,undo log和read view。通过读取undo log的历史版本数据,来实现不同事务拥有自己独立的快照数据版本。
    主要过程:获得事务版本号;获取一个read view;查询到数据,与read view事务版本号进行批评;不符合read view规则的从undolog里获取历史版本数据;返回符合规则的数据。
    Read View, 每个sql执行前,都会得到一个read_view,主要保存了当前数据库系统中正处于活跃(没有commit)的事务ID号。不同隔离级别下,根据不同的条件匹配read view。RC隔离级别下,同一个事务里的每一次查询都会获得一个新的read view副本,这样既可能造成同一个事务里前后读取数据可能不一致问题(重复读)。RR级别下,一个事务里只会获取一次read view副本,从而保证每次查询的数据都是不一致的。Read Uncommitted级别下,事务不会获取read view副本。
    事务版本号, 事务开始前都会从数据库获取一个自增长的事务ID,可以根据事务ID判断事务的执行先后顺序。

    5.phantom problem幻读问题
    解决:mvcc+next-key lock?为什么mvcc不够?
    什么是phantom problem?
    RR隔离级别下,全部使用快照读不会存在幻读问题,但是快照读和当前读混用时会存在幻读问题,因此需要间隙锁机制。
    实例如下:

    a. 事务A执行SELECT * FROM T WHERE ID>3 返回ID=3和4两条记录;
    b. 事务B插入了一条ID=5的数据并commit;
    c.事务A执行SELECT * FROM T WHERE ID>3 得到3、4两条记录;
    d. 事务A执行UPDATE T SET NAME='..' WHERE ID>3 更新三条记录;
    e. 事务A再次执行SELECT * FROM T WHERE ID>3 得到3、4、5三条记录。
    

    a和c两步均为快照读,因此都只能读取到两条记录,且不对数据加锁。b中插入ID=5的数据,在d中是可见的,因为UPDATE中使用当前读读取最新commit的数据,如果不使用当前读会有数据覆盖问题。因为事务A在d中对ID=5的数据进行了更新,因此A中快照进行了更新,e中可以查询到5这条记录。
    事务A中快照读和当前读混用,导致了幻读问题。如果都使用当前读即a中使用SELECT FOR UPDATE,则b中插入操作会阻塞,A中的update和select都始终只会影响两条记录。当前读使用了间隙锁,因此会阻塞b中操作。

    快照读和当前读 快照读是读取数据时,不读取最新版本数据,而是基于历史版本读取的一个快照信息(innodb基于undo log历史版本),快照读可以使用普通的SELECT读取数据不用对表数据进行加锁。当前读是读取最新的数据,需要对数据加锁。update、delete、insert、select ... lock in share mode和select ... for update 为当前读。RC隔离级别下,只加record lock,而在RR下加Record Lock。

    6.参考
    《MySQL内幕:Innodb存储引擎》大名鼎鼎,但是很多东西讲的不清楚,遗憾。
    意向锁
    各种锁,幻读

  • 相关阅读:
    个人学习随笔(psi-blast随笔)
    psp进度(11月25号-31号)
    本周psp(11月17-23)
    规格说明书练习-吉林市1日游
    补PSP进度(10.28-11.3)
    第九周PSP&进度条
    (第九周)读构建之法有感1
    (第九周)读构建之法有感2
    词频统计的效能测试。
    (第二周)读《构建之法》有感
  • 原文地址:https://www.cnblogs.com/rainsbaby/p/13910152.html
Copyright © 2011-2022 走看看