MySQL原理及优化(三)MySQL事务与锁详解

MySQL事务与锁详解

什么是数据库的事务

事务的定义

事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
注意:第一,事务是数据库的最小工作单元,是不可再分的。第二,它可能包含一个或者一系列的DML语句。

事务的四大特性

原子性(Atomicity)

不可再分,意味着对数据库的操作要么成功,要么失败。失败的时候要进行回滚。
原子性,在InnoDB里面是通过undo log来实现的,它记录了数据修改之前的值(逻辑日志),一旦发生议程,就可以用undo log来实现回滚操作。

一致性(Consistent)

数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。比如主键必须是唯一的,字段长度符合要求等。

隔离性(Isolation)

很多事务对表或者行的并发操作,应该是透明的,互相不干扰的。通过这种方式,保证业务数据的一致性。

持久性(Durable)

对数据库的任意操作,只要是事务提交成功,那么结果就是永久性的。不肯那个因为系统宕机或者重启又恢复了原来的状态。这就是事务的持久性。

持久性的实现、数据库崩溃恢复(crash-safe)的实现:
持久性是通过redo log和double write双写缓存来实现的。我们操作数据的时候,会先写到内存的buff pool里面,同时记录redo log,如果刷盘之前出现异常,在重启后就可以读取redo log的内容,写入到磁盘,保证数据的持久性。恢复成功的前提是数据页本身没有破坏,是完整的,这个通过双写缓存(double write)保证。

事务的原子性、隔离性、持久性最后都是为了实现一致性。

事务并发带来的问题

脏读

在这里插入图片描述

不可重复读

在这里插入图片描述

幻读

在这里插入图片描述

无论是脏读,还是不可重复读,还是幻读,他们都是数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况。
读一致性的问题必须要有数据库提供一定的事务隔离机制来解决。

事务隔离级别

SQL92标准制定了四个隔离级别用来解决事务并发的问题。

  • Read Uncommitted(未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以佳作RU,它没有解决任何问题。
  • Read Committed(已提交读),一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。
  • Repeatable(可重复读),解决不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结构是一样的,但是没有定义解决幻读问题。
  • Serializble(串行化),所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作,所以它解决了所有的问题。

InnoDB对隔离级别的支持

在这里插入图片描述
InnoDB支持四个隔离级别和SQL92定义的基本一致,隔离的级别越高,事务的并发度就越低。唯一的区别在于InnoDB在RR的级别解决了幻读的问题。因此InnoDB默认使用RR作为事务隔离级别,既保证了数据的一致性,又支持较高的并发度。

LBCC

基于锁的并发操作(Lock Based Concurrency Control):要保证前后两次读取数据一致,在读取数据的时候锁定我要操作的数据,不允许其他的事务修改。
如果仅仅是基于锁来实现事务隔离,一个事务读取的时候不允许其他事务修改,那么意味着不支持读写操作,而我们的大多数应用都是读多写少的,这样会极大地影响操作数据的效率。

MVCC

如果要让一个事务前后两次读取的数据保持一致,那么可以在修改数据的时候建立一个备份或快照,后面再来读取这个快照就行了。这种方案叫做多版本的并发控制Multi Version Concurrency Control。

MVCC的核心思想是,我可以查找在我这个事务开始之前已经存在的数据,即使它在后面被修改或删除了。在我之歌事务之后新增的数据,我是查不到的。

快照什么时候创建?读取的时候怎么保证能读取到这个快照而不是最新的数据?
InnoDB为每行记录都实现了两个隐藏字段:

  • DB_TRX_ID(6字节):插入或更新行的最后一个事务的事务ID,事务编号是自动递增的。(创建版本号,在数据新增或者修改为新数据的时候,记录当前事务ID)
  • DB_ROLL_PTR(7字节):回滚指针(删除版本号,数据被删除或记录为旧数据的时候,记录当前事务ID )

MVCC的查找规则:智能查询创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行。

在InnoDB中,MVCC是通过undo log实现的。

MySQL InnoDB锁的基本类型

行级锁:Shared and Exclusive Locks,表级锁:Intention Lockes 称为锁的基本模式。
Record Locks,Gap Locks、Next-key Locks叫做锁的算法。

锁的粒度

表锁:锁住一张表;行锁:锁主表里面的一行数据。锁定粒度:表锁大于行锁。

加锁效率:表锁的加锁效率大于行锁的加锁效率。

冲突概率:表锁的冲突概率大于行锁的冲突概率。

基本锁的模式

共享锁(Shared Locks)

获取一行数据的读锁以后,可以用来读取数据后,可以用来读取数据。
注意:不要在加了读锁以后写数据,不然可能会造成死锁的情况。多个书屋可以共享一把读锁。

加读锁:

select ...... lock in share mode;

释放锁有两种方式:事务结束,锁就会自动释放,包括提交事务和结束事务。

排它锁(Exclusive Locks)

排它锁使用来来操作数据的,所以又叫做写锁。只要一个事务获取了一行数据的排它锁,其他事务就不能再获取这一行数据的共享锁和排它锁。

排它锁的加锁方式有两种,一种是自动加排它锁:在操作数据的时候(insert、update、delete)都会默认加上一把排它锁。还有一种方式是手工加锁。

加写锁

update ...... FOR UPDATE

意向锁

意向锁是由数据库自己维护的。当给一行数据加上共享锁之前,数据库会自动在这张表上加一个意向共享锁;当给一行数据加上排它锁之前,数据库会自动在这张表上加一个意向排它锁
如果一张表上至少有一个意向共享锁,说明其他的事务给其中的某些数据行加上了共享锁;如果一张表上至少有一个意向排它锁,说明其他的事务给其中的某些数据行加上了排它锁。

行锁的原理

InnoDB的行锁锁住的是索引,如果没有索引,会锁住整张表。
因为查询没有使用索引,会进行全表扫面,把每一个隐藏的聚集索引都锁住了。

给唯一索引加锁,主键索引也会被锁住:通过辅助索引锁定一行数据的时候,步骤跟检索数据一样,会通过主键值找到主键索引,然后锁定。

锁的算法

在这里插入图片描述
测试表Test1,表中有一个主键索引。插入4行数据,主键值分别是1、4、7、10。因为使用的是主键索引加锁,划分标准就是主键索引的值。

  • 表中存在的主键值,教唆Record(记录),这里有4个Record。
  • 根据主键,这些存在的Record隔开的数据不存在的区间,叫做Gap(间隙),它是一个左开右开的区间。
  • 间隙联通它左边的记录,叫做临键区间,它是左开右闭的区间。

记录锁

使用唯一的索引(包括唯一索引和主键索引)使用等值查询,精确匹配到一条记录的时候,这个时候使用的是记录锁。
使用不同的key去加锁,不会冲突,它只锁住了这个record。

间隙锁

当查询的记录不存在,没有命中任何一个Record,无论是等值查询还是范围查询,使用的是间隙锁。
间隙锁只要是阻塞插入insert。相同的间隙锁之间不冲突。

间隙锁只在RR中存在,如果要关闭间隙锁,就要把事务隔离级别设置成RC,并把innodb_locks_unsafe_for_binlog设置成ON。

临键锁

使用了查询范围,不仅命中了Record记录,还包含了Gap,在这种情况下我们使用的就是临键锁,它是MySQL里面默认的行锁算法,相当记录锁加上间隙锁。

唯一索引,等值查询匹配到一条记录的时候,退化成记录锁;没有匹配到任何记录的时候退化成间隙锁。

InnoDB的RR级别能够解决幻读问题就是使用临键锁来实现的。

死锁

锁的释放

锁在事务结束(commit、rollback)、客户端断开时进行释放。

死锁的发生和检测

  1. 同一时刻智能友一个事务持有这把锁;
  2. 其他事务需要在这个事务释放锁之后才能获取锁,而不可以强行剥夺。
  3. 当多个事务形成等待环路的时候,即发生死锁。

死锁的避免

  1. 在程序中,操作多张表时,尽量以相同的顺序访问(避免形成等待环路);
  2. 批量操作单张表数据的时候,先对数据进行排序(避免形成等待环路);
  3. 申请足够级别的锁,如果操作数据,申请排它锁。
  4. 尽量使用索引访问数据,避免没有where条件的操作,没避免锁表。
  5. 大事务化小事务
  6. 使用等值查询而不是范围查询查询数据,命中记录,避免间隙锁对并发的影响。

猜你喜欢

转载自blog.csdn.net/baidu_41934937/article/details/108792841