四步趟过MySQL之第三步:MySQL事务学习

138 阅读9分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

首先,各位技术人,情人节快乐~

//情人节一个人和电脑过,我一点都不难受,真的……

一、事务

1.1 事务的定义

复习一下,什么是事务?

维基百科:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

通俗:就是最小的一个或者一组数据库操作语句,要么都成功要么都失败

1.2 事务的四大特性

原子性(Atomicity):

最小,不可再分 InnoDB通过undo log来实现。

隔离性(Isolation):

各个事务之间互不影响互不干扰,

持久性(Durability):

只要事务提交了,就不能因为断电之类的情况导致不能持久 redo log和双写缓冲(doble write buffer)来实现

一致性(Consistent):

原子性、隔离性、持久性都是为了实现一致性。 一致性,指的是数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。

1.3 数据库操作事务

1.3.1 事务自动提交/回滚

1、增删改语句会自动开启事务并提交;

InnoDB里面有一个autocommit的参数(分为两个级别,session 级别和global级别)。默认值是on/true show variables like 'autocommit';

2、客户端连接断开的时候,未提交的事务会自动回滚

1.3.2 手动开启事务

begin;/start transaction;
select * from xxx 
commit/rollback;

1.4 并发带来的事务问题

脏读:一个事务读到其他事务未提交的数据;

不可重复读:一个事务读到其他事务已提交的修改或删除操作

幻读:一个事务读到其他事务已提交的新增操作

这三个都是属于数据库的读一致性问题;

1.5 数据库提供事务隔离机制来解决读一致性问题

美国国家标准协会(ANSI)制定的SQL92标准

image.png

第一个隔离级别叫做: Read Uncommitted (未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做RU,它没有解决任何的问题。

第二个隔离级别叫做: Read Committed (已提交读),也就是一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。

第三个隔离级别叫做: Repeatable Read (可重复读),它解决了不可重复读的问题,也就是同一个事务里面多次读取同样的数据结果是一样的, 但是在这个级别下,没有定义解幻读的问题。(InnoDB默认级别,并且解决了幻读问题)

最后一个就是: Serializable (串行化),在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题。

二、MySQL数据库(InnoDB)是怎么实现事务隔离的

image.png

数据库想要实现事务隔离有两个方案:

LBCC:基于锁的并发控制

MVCC:基于快照机制实现,在修改数据之前建立一个快照,这种就叫多版本的并发控制 Multi Version Concurrency Control

2.1 MVCC

1、首先,数据库中每条数据都有两个隐藏字段, image.png

DB_ TRX_ ID:6字节:事务ID,数据是在哪个事务插入或者修改为新数据的,就记录为当前事务ID。

DB_ROLL_PTR:指向该条数据在undo log中形成的 undo log链(数据库中的数据进行修改和删除时都会记录到undo log中,多次操作就会形成该条数据的undolog链)

2、每个事务在进行第一次查询之后都开始维护自己的一个数据结构——Read View(可见性视图)

另外,RR和RC的区别就是:RR中Read View是事务第一次查询的时候建立的。RC 的Read View是事务每次查询的时候建立的。

image.png

3、这样当前事务在进行查询的时候,首先将当前事务的事务ID和数据的DB_ TRX_ ID、DB_ROLL_PTR进行比较,先根据事务可见性规则判断 DB_ TRX_ ID,如果不符合显示当前数据版本条件,就去DB_ROLL_PTR所指向的undo log链中寻找符合条件的数据来显示

事务判断可见性的规则为:

image.png

2.2 LBCC

锁的作用是什么?

Java中加锁是为了解决对象资源的竞争

数据库就是为了解决数据表资源或者数据行资源的竞争

2.2.1 锁的粒度

InnoDB支持行锁和表锁 MyISAM支持表锁

表锁和行锁的区别:

锁定粒度:表锁 (大)> 行锁 (小)

加锁效率:表锁 (高)> 行锁 (低)

冲突概率:表锁 (大)> 行锁 (小)

并发性能:表锁 (低)< 行锁 (高)

附:怎么手动锁整张表?

lock tables xxx read;
lock tables xxx write;
unlock tables;

2.2.2 InnoDB的锁

image.png

我们可以看到,官网把锁分成了8类。我们把前面的两个行级别的锁(Shared andExclusive Locks),和两个表级别的锁(Intention Locks)称为锁的基本模式。

后面三个Record Locks. Gap Locks、Next-Key Locks,我们把它们叫做锁的算法,也就是分别在什么情况下锁定什么范围。

image.png

2.2.3 共享锁(Shared Locks)

又称为读锁(S锁),共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改;

注意:不要在加了读锁以后去写数据,不然可能会出现死锁的情况,而且多个事务可以共享一把读锁

我们可以用select .... lock in share mode;的方式给某些数据手工加上一把读锁。

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

1、读锁会和写锁相互排斥;

2、读锁之间不会排斥,一个事务给这条事务加了读锁,其他事务也还是可以给这条数据加读锁的;

2.2.4 排它锁(Exclusive Locks)

又称为写锁(X锁),排他锁不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的锁(共享锁、排他锁), 只有该获取了排他锁的事务是可以对数据行进行读取和修改。

加锁方式: 自动:delete | update | insert默认加上X锁; 手动:select * from student where id=1 FOR UPDATE; 解锁方式也是事务提交或回滚;

附加: 如果该条数据被其他事务锁住了导致我无法获取锁或者操作增删改,会等待多久?默认50s

show VARIABLES like '%wait%' image.png

2.2.5 意向锁

意向锁是由数据引擎自己维护的,用户无法手动操作意向锁。

如果我给一张表中的一行数据加了S锁或者X锁,那数据引擎会自动在这张表上增加一个意向锁,这样我再想锁整表的时候,就不需要遍历每行数据看看有没有加锁,(火车上厕所看到灯亮了就知道有人在里边)

2.2.5.1 意向共享锁(Intention Shared Lock,简称IS锁)

表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁。

2.2.5.2 意向排他锁(Intention Exclusive Lock,简称IX锁)

表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

2.2.6 行锁的原理

行锁锁的到底是啥?

锁住的是索引(个人理解是锁住的聚集索引)

新建三张表来验证,每张表分为两个字段id,name;

m1:没有主键索引没有唯一索引

第一个事务锁住id=1,第二个事务试图锁住id=2,第二个事务加锁失败(锁住了整张表), 所以:没有索引的表没有办法加行锁,只能锁整张表(虽然根据规则,innoDB的表一定会有聚集索引,没有主键没有非空唯一索引就会使用隐藏字段生成聚集索引,但是你无法获取到隐藏ROWID)

m2:id作为主键索引

第一个事务锁住id=1,第二个事务试图锁住id=2,第二个事务加锁成功(只锁住id=1的数据,行锁),

m3:id作为主键索引,name为唯一索引

第一个事务锁住name=1,第二个事务试图锁住name=1或者id=1,第二个事务加锁失败,但是可以锁住name=2或者id=2(根据之前学习上一篇索引结构,可以得知非聚集索引的二级索引叶子节点存储的是聚集索引的键值,所以给其他唯一索引加锁也是可以锁行的) 另外,不管是否有索引的表,如果加锁的时候没有精确使用索引列搜索,也是会直接锁表的,比如m2表,用name列去加锁,是会锁整张表的。这也可以解释为什么验证m1表的时候会锁表,你没办法使用隐藏ROWID去加锁(因为查询没有使用索引,会进行全表扫描,然后把每一个隐藏的聚集索引都锁住了)

2.2.7 锁的算法

在了解锁的算法之前,先要了解几个概念

image.png

Record:记录(数据库里面存在的主键值)

Gap:间隙(已有主键间的区间叫Gap,左开右开的区间,N+1段)

Next-key:临键(间隙和左边的记录,左开右闭的区间叫临键)

2.2.7.1 记录锁(Record Lock)

我们对于唯一性索引(主键索引,唯一索引)使用等值查询并且匹配到记录,这个时候InnoDB使用的锁就是记录锁

2.2.7.2 间隙锁(Gap Lock)

当我们使用等值查询或者范围查询的时候没有匹配到记录,这个时候InnoDB使用的就是间隙锁。间隙锁主要时阻塞插入(insert)操作,相同的间隙锁之间不冲突。

2.2.7.3 临键锁(Next-Key Lock)

当我们使用范围查询的时候,不仅命中了记录,还命中了间隙,这种情况InnoDB加的就是临键锁,他是MySQL里面默认的行锁算法,相当于记录锁加上间隙锁。

2.2.7.4 什么情况下会死锁

1.同一时刻只能有一 个事务持有这把锁;(互斥)

2.其他的事务需要在这个事务释放锁之后才能获取锁,而不可以强行剥夺;

3.当多个事务形成等待环路的时候,即发生死锁。

2.2.7.5 查看锁信息(日志)

`show status like 'innodb_row_lock%';`

image.png

//没想到吧 这里也能看到todo,写不完了,困了,明天再说吧~

//TODO:怎么详细查看锁的情况

//TODO:怎么避免死锁