zoukankan      html  css  js  c++  java
  • 数据库隔离级别实现原理及锁介绍

    一、事务隔离级别介绍

    众所周知,MySQL 数据库事务的隔离级别有4个,由低到高依次为 Read Uncommitted(读未提交)、Read Committed(读已提交)、Repeatable Read(可重复读)、Serializable(串行化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。MySQL 默认的隔离级别是可重复读。

    事务隔离级别 脏读 不可重复读 幻读
    未提交读
    提交读 ×
    可重复读 × ×
    串行化 × × ×

    那么 MySQL 是如何实现几种隔离级别的呢?

    二、各级别的实现原理

    读未提交(RU)

    在 RU 级别中,事务读到的所有数据都是最新的数据,可能是事务提交后的数据,也可能是事务执行中的数据(可能会被回滚)。

    当隔离级别为 RU 时:

    • 所有的读不加锁,读到的数据都是最新的数据,性能最好。
    • 所有的写加行级锁,写完释放。

    读已提交(RC)

    使用 MVCC(多版本并发控制) 技术,在每一行加入隐藏的字段(DB_TRX_ID:修改该行的最后一个事务的id,DB_ROLL_PTR:指向当前行的undo log日志,DB_ROW_ID:行标识,DELETE_BIT:删除标志),它实现了不加锁的读操作。

    当隔离级别为RC时:

    • 写操作:加行级锁。事务开始后,会在 UNDO 日志中写入修改记录,数据行中的隐藏列 DATA_POLL_PTR 存储指向该行 UNDO 记录的指针。
    • 读操作:不加锁。在读取时,如果该行被其它事务锁定,则顺着隐藏列 DATA_POLL_PTR 指针,找到上一个有效的历史记录(有效的记录:该记录对当前事务可见,且 DELETE_BIT=0 )。

    可重复读(RR)

    使用MVCC技术来实现不加锁的读操作。

    当隔离级别为RR时:

    • 写操作:加行级锁。事务开始后,会在 UNDO 日志中写入修改记录,数据行中的隐藏列 DATA_POLL_PTR 存储指向该行的 UNDO 记录的指针。
    • 读操作:不加锁。在读取时,如果该行被其它事务锁定,则顺着隐藏列 DATA_POLL_PTR 指针,找到上一个有效的历史记录(有效的记录:该记录对当前事务可见,且 DELETE_BIT=0 )。

    从上面可以知道:实际上RC和RR级别的操作基本相同,而不同之处在于:行记录对于当前事务的可见性(可见性:即哪个版本的行记录对这个事务是可见的)。RC级别对数据的可见性是该数据的最新记录,RR基本对数据的可见性是事务开始时,该数据的记录。

    1) 行记录的可见性(read_view)的实现

    在 innodb 中,创建一个事务的时候,会将当前系统中的活跃事务列表创建一个副本(read_view),里面存储着的都是在当前事务开始时,还没commit的事务,这些事务里的值对当前事务不可见。read_view中有两个关键值 up_limit_id(当前未提交事务的最小版本号 -1,在 up_limit_id 之前的事务都已经提交,在 up_limit_id 之后的事务可能提交,可能还没提交) 和 low_limit_id(当前系统尚未分配的下一个事务id,也就是目前已出现过的事务 id 的最大值 +1 。注意: low_limit_id 不是最大的活跃事务的 id 。)

    注意:当前事务和正在 commit 的事务是不在 read_view 中的。

    2)无论是RC级别还是RR级别,其判断行记录的可见性的逻辑是一样的。

    当该事务要读取 undo 日志中的行记录时,会将行记录的版本号(DB_TRX_ID)与 read_view 进行比较:

    1. 如果 DB_TRX_ID 小于 up_limit_id,表示该行记录在当前事务开始前就已经提交了,并且DELETE_BIT=0,则该行记录对当前事务是可见的。

    2. 如果 DB_TRX_ID 大于 low_limit_id,表示该行记录在所在的事务在本次事务创建后才启动的,所以该行记录的当前值不可见。

    3. 如果 up_limit_id <= DB_TRX_ID <= low_limit_id,判断 DB_TRX_ID 是否在活跃事务链中,如果在就是不可见,如果不在就是可见的。

    4. 如果上面判断都是不可见的,则读取 undo 日志中该行记录的上一条行记录,继续进行判断。

    而对于 RC 级别的语句级快照和 RR 级别的事务级快照的之间的区别,其实是由 read_view 生成的时机来实现的。RC 级别在执行语句时,会先关闭原来的 read_view,重新生成新的 read_view。而 RR级别的 read_view 则只在事务开始时创建的。所以 RU 级别每次获取到的都是最新的数据,而RR级别获取到的是事务开始时的数据。

    3)值得注意的是:在上面的可见性判断中,虽然逻辑是一样的,但是实际意义上是有区别的:

    在第二步中,对于 RC 级别来说,low_limit_id 是执行语句时已出现的最大事务id+1,可以认为在执行语句时,是不存在事务会比low_limit_id 要大,所以大于 low_limit_id 的事务都是不可见的。而对于 RR 级别来说,low_limit_id 是当前事务开始时已出现的最大事务+1(也可以认为是当前事务的id+1,因为在创建当前事务时,当前事务的id最大),大于 low_limit_id 的事务表示是在该事务开始后创建的,所以对RR级别是不可见。

    在第三步中,对于 RC 级别来说,只要 DB_TRX_ID 不在活跃链表中,则无论 DB_TRX_ID 是否大于事务 id,RC 都是可见的。而对于 RR 级别来说,因为 low_limit_id 就是**当前事务 **id + 1,可以认为小于 low_limit_id 的事务都是在当前事务创建前出现的,所以也只需要简单判断 DB_TRX_ID 是否在活跃链表中。

    串行化:读写都会加锁。

    三、MySQL中的锁介绍

    锁分类

    Mysql为了解决事务并发,数据安全问题,使用了锁机制。

    按照锁的粒度可以把锁分为表级锁和行级锁

    1表级锁

    Mysql中粒度最大的一种锁,会锁住当前操作的整张表并发性能低,但表锁的实现简单,耗费资源少,加锁快,不会出现死锁

    2行级锁

    Mysql中粒度最小的一种锁,只会锁住当前操作的数据行。行锁极大地提高了Mysql的并发性能,但行锁的开销较大,速度较慢,会出现死锁

    按照锁的性质可以把锁分为共享锁和排它锁

    1共享锁(S锁)

    其他事务可以读取被共享锁锁住的数据行,不能修改该数据行,并且也只能对该数据行加共享锁,而不能加排它锁。

    2排它锁(X锁)

    当一个事务对数据行加上排他锁,那么该事务可以读取和修改该数据行,而其他事务不允许对该数据行加任何锁。

    表锁

    Mysql中表锁除了共享锁和排他锁之外,还存在着两种锁:意向共享锁(IS),意向排他锁(IX)。

    意向锁的作用是表明该事务想对该表加一个共享/排他锁,但并没有真正把锁加上去。比如,当事务想对一个被排他锁锁住的表加上共享锁/排他锁时,必须先在该表上添加一个意向共享锁/意向排他锁,直到锁住表的排他锁被释放。

    事务在给一个数据行加共享锁前必须先取得该表的IS,在加排它锁前必须先取得该锁的IX。并且意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在

    行锁

    InnoDB行锁是通过给索引上的索引项加锁来实现的,这意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。

    Innodb中的行级锁有以下几种:

    1Record Lock: 对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项;

    2Gap Lock: 对符合条件范围的“间隙”加锁,锁定记录的范围,不包含索引项本身。其他事务不能在锁范围内插入数据。“间隙(GAP)”是指键值在条件范围内但并不存在的记录。

    3Next-key Lock锁定索引项本身和间隙。Record Lock和Gap Lock的结合,Next-key Lock就是我们所说的间隙锁,可解决幻读问题。

    举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL:

    Select * from emp where empid > 100 for update;

    是一个范围条件的检索**,InnoDB 不仅会对符合条件的 **empid值为 101 的记录加锁,也会对 empid 大于 101 (这些记录并不存在)的“间隙”加锁。

    在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入。

    注意

    1) InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使用的是Next-Key Lock 锁,解决的幻读的问题,因此InnoDB 引擎的默认的隔离级别 REPEATABLE-READ(可重读) 已经达到了SQL标准的 SERIALIZABLE(可串行化) 隔离级别,并且事务不需要串行化。

    2) 当查询的索引含有唯一属性时,将next-key lock降级为record key。

    3) Innodb中行级锁是加在索引上,所以只有使用索引时,才会加行锁,否则只会加表锁。但这并不意味着,只要使用了索引就会加行级锁,如果MySQL认为全表扫描效率更高,比如对一些很小的表或者索引范围包括大部分表数据,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。

    死锁

    不同于MyISAM总是一次性获得所需的全部锁,InnoDB的锁是逐步获得的,当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。 发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务,我们可以采取以上方式避免死锁:

    1) 通过表级锁来减少死锁产生的概率;

    2) 多个程序尽量约定以相同的顺序访问表;

    3 )同一个事务尽可能做到一次锁定所需要的所有资源。

    原文:https://blog.csdn.net/qecode/article/details/97274409

  • 相关阅读:
    Could not load file or assembly 'Microsoft.Practices.EnterpriseLibrary.Common, Version=5.0.414.0, ...
    问题:ORA-28000: the account is locked 用户锁住了。
    oracle连接数据库报错:ORA-01034: ORACLE not available(Oracle 不存在),ORA-27101: shared memory realm does not exist
    数据抽取Sql语句
    在Eclipse中部署Maven多模块项目
    Struts 学习记录
    eclipse中git插件无法向远程仓库提交tag的问题
    goldGrid-VBA-EXCLE处理
    SqlBulkCopy效率低下原因分析
    各种奇葩小问题
  • 原文地址:https://www.cnblogs.com/hanstrovsky/p/14318952.html
Copyright © 2011-2022 走看看