MySQL事务和锁

2121.png

一、MySQL事务

1.1、什么是事务

  是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);MySQL的众多存储引擎中,只有InnoDB支持事务。

1.2、事务的四大特征ACID

  • 原子性(ATOMICITY):一个事务要被完全的无二义性的做完或撤消。在任何操作出现一个错误的情况下,构成事务的所有操作的效果必须被撤消,数据应被回滚到以前的状态。

  • 一致性(CONSISTENCY):一个事务应该保护所有定义在数据上的不变的属性(例如完整性约束)。在完成了一个成功的事务时,数据应处于一致的状态。换句话说,一个事务应该把系统从一个一致-状态转换到另一个一致状态。举个例子,在关系数据库的情况下, 一个一致的事务将保护定义在数据上的所有完整性约束。

  • 隔离性(ISOLATION):在同一个环境中可能有多个事务并发执行,而每个事务都应表现为独立执行。串行的执行一系列事务的效果应该同于并发的执行它们。这要求两件事:

    1. 在一个事务执行过程中,数据的中间的(可能不一致)状态不应该被暴露给所有的其他事务。
    2. 两个并发的事务应该不能操作同一项数据。数据库管理系统通常使用锁来实现这个特征。
  • 持久性(DURABILITY):一个被完成的事务的效果应该是持久的。

1.3、隔离级别

  • 读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用)
  • 读已提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。
  • 可重复读:同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在。
  • 串行化:事务串行执行。避免了以上所有问题。

以上是SQL-92标准中定义的四种隔离级别。在MySQL中,默认的隔离级别是REPEATABLE-READ(可重复读),并且解决了幻读问题。简单的来说,mysql的默认隔离级别解决了脏读、幻读、不可重复读问题。不可重复读重点在于update,而幻读的重点在于insert、delete。

1.4、MVCC

MVCC的全称是“多版本并发控制”。这项技术使得InnoDB的事务隔离级别下执行一致性读操作有了保证,换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值。这是一个可以用来增强并发性的强大的技术,因为这样的一来的话查询就不用等待另一个事务释放锁。这项技术在数据库领域并不是普遍使用的。一些其它的数据库产品,以及mysql其它的存储引擎并不支持它。

InnoDB会给数据库中的每一行增加三个字段,它们分别是DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。

增删查改

  • SELECT 读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。
  • INSERT 将当前事务的版本号保存至行的创建版本号
  • UPDATE 新插入一行,并以当前事务的版本号作为新行的创建版本号,同时将原记录行的删除版本号设置为当前事务版本号
  • DELETE 将当前事务的版本号保存至行的删除版本号

快照读和当前读

快照读:读取的是快照版本,也就是历史版本

当前读:读取的是最新版本

普通的SELECT就是快照读,而UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE是当前读。

1.5、脏读、幻读、不可重复读

  • 脏读(Dirty Reads)

读取了未提交的数据:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。

例子:B修改了订单详情,还未提交事务,A查看详情(读的是B修改后的数据),此时B事务出错,进行回滚。A查看的数据就是脏的(假数据)。

  • 不可重复读(Non-Repeatable Reads)

前后多次读取,数据内容不一致:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

例子:事务A修改用户姓名,此时年龄为20,事务B修改年龄为25,等事务A修改完再查询,发现年龄由20变成了25。此时事务B回滚,A再返回页面发现年龄变回了20,多次读取,年龄20-25-20,重复读出来的数据内容不一致。

  • 幻读(Phantom Read)

前后多次读取,数据内容不一致

例子:A将数据库中所有学生的成绩从具体分数重置为0,但是B就在这个时候插入了一条具体分数100的记录,当A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

二、MySQL锁

2.1、共享锁 Shared Locks(S锁)

加了S锁的记录,允许其他事务再加S锁,不允许其他事务再加X锁

加锁方式:select…lock in share mode

2.2、排他锁 Exclusive Locks(X锁)

加了X锁的记录,不允许其他事务再加S锁或者X锁

加锁方式:select…for update

2.3、意向锁 Intention Locks

意向锁是表锁,多用在innoDB中,是数据库自身的行为,不需要人工干预,在事务结束后会自行解除。

意向锁分为意向共享锁(IS锁)和意向排它锁(IX锁)

IS锁:表示事务中将要对某些行加S锁
IX锁:表示事务中将要对某些行加X锁
意向锁的主要作用是提升存储引擎性能,innoDB中的S锁和X锁是行锁,每当事务到来时,存储引擎需要遍历所有行的锁持有情况,性能较低,因此引入意向锁,检查行锁前先检查意向锁是否存在,如果存在则阻塞线程。

意向锁的使用

举个栗子🌰:

T1:
SELECT * FROM A WHERE id = 1 lock in share mode;(加S锁)
T2:
SELECT * FROM A WHERE id > 0 for update; (加X锁)

看上面这2个SQL事务,T1执行时候,对id=1这行加上了S锁,T2执行前,需要获取全表的更新锁进行判断,即:	

**step1:**判断表A是否有表级锁
**step2:**判断表A每一行是否有行级锁
当数据量较大时候(我们一张表一般500-5000万数据),step2这种判断极其低效。

意向锁协议

  • 事务要获取表A某些行的S锁必须要获取表A的IS锁
  • 事务要获取表A某些行的X锁必须要获取表A的IX锁

这个时候step2就改变成了对意向锁的判断

step2发现表A有IS锁(意向锁是表锁,T1加S锁,必然获得IS锁),说明表肯定有行级的S锁,因此,T2申请X锁阻塞等待,不需要判断全表,判断效率极大提高(是不是省了很多钱)

2.4、间隙锁

当我们用范围条件而不是相等条件检索数据(非聚簇索引、非唯一索引),并请求共享或排他锁时,InnoDB会给符合条件的数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做 “间隙(GAP)”,InnoDB也会为这些 “间隙” 加锁,即间隙所,这种锁机制就是所谓的Next-key锁。
Next-Key锁:符合条件的行锁 + 间隙锁

间隙锁产生的条件

在InnoDB下,间隙锁的产生需要满足三个条件:

  1. 隔离级别为RR 2.当前读
  2. 查询条件能够走到索引

间隙锁的作用

MySQL官方文档:间隙锁的目的是为了让其他事务无法在间隙中新增数据。

在RR模式(事务隔离级别:Repeatable Read 可重复读 )的InnoDB中,间隙锁能起到两个作用:

  1. 保障数据的恢复和复制

  2. 防止幻读

  • 防止在间隙中执行insert语句
  • 防止将已有数据update到间隙中

数据库数据的恢复和复制是通过binlog实现的,binlog中记录了执行成功的DML语句,在数据恢复时需要保证数据之间的事务顺序,间隙锁可以避免在一批数据中插入其他事务。

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等的条件来访问更新数据,避免使用范围条件。

还要特别说明的是,InnoDB除了通过范围条件加锁时使用Next-Key锁外,如果使用相等条件请求给一个并不存在的数据记录加锁,InnoDB也会使用Next-Key锁。


已有 0 条评论

    欢迎您,新朋友,感谢参与互动!