事务四大特性

原子性

一致性

持久性

隔离性

事务并发带来的问题

脏读

事务1读取到了事务2未提交的数据
Dingtalk_20220310102942.jpg
事务B读到了事务A修改还没commit的数据
例如:事务A将name=2更改成3 但是没有提交 事务B读取到了3 但是此时事务A出错回滚 这样读到的就是脏数据

脏写

A 事务新增了值 B事务又新增了值 A事务将值回滚 B事务拿不到值

不可重复读

Dingtalk_20220310103048.jpg
事务A将name =2 改成3 事务B先读到了name =2的值 在更改之后又读到了name =3的值导致了两次读取结果不一样

幻读

Dingtalk_20220310103233.jpg
事务B读到了事务A新增还没commit的数据
事务A name =2 事务B读取到了2 事务A又增加了一条数据 事务B又读到了增加的数据 导致前后两次读取的结果不一致

事务隔离级别

读未提交

解决了脏写

读已提交(RC)

解决了脏读的问题

解决方案

每次提交都会创建ReadView

可重复读(RR)

解决了不可重复读 InnoDB解决了幻读的问题

解决方案

事务第一次查询的时候创建

串行化

全都解决 效率极低不推荐使用

事务一致性方案

MVCC(多版本并发控制)

https://www.51cto.com/article/641019.html
修改数据之前加了备份或者快照 以后都读取快照就行

MVCC的原则

一个事务能看到:
1.本事务修改的数据
2.第一次查询之前已经提交的事务的修改
4.删除id 大于自己和修改id 小于自己的
一个事务不能看到 :
1.本事务之后提交的事务(事务ID比本事务大)
2.未提交的事务的修改
3.删除id 小于自己

MVCC的效果

可以查到事务开始之前的数据 即使被修改或删除
后面新增的数据查不到

原理

InnoDB为每行记录有两个隐藏字段
1. DB_TRX_ID(事务ID)
数据是在哪个事务插入或者修改为新事务的就记录事务ID
2.DB_ROLL_PTR(回滚指针 删除版本号)
删除的时候记录事务的ID

RR流程

1.第一个事务初始化数据 id =1 name =2 ,id =2 name =3 事务id =1
2.第二个事务读取 因为是之前已经初始化的数据所以都能读到 事务id =2
3.第三个事务插入数据事务ID =3事务二读取 因为只能读到事务id 小于等于当前事务的 和删除事务大于当前事务的 所以看不到
4.第四个事务 删除数据 事务ID=4 第二个事务再去查询能查到 因为 删除事务ID大于当前事务
5.第五个事务执行更新操作 会先去把name =2 的删除事务标记更改 然后新增一条事务视剧记录 第二个事务再去读取 修改之前的那条记录能读到 因为删除事务ID 比当前事务ID 大 但是修改时新增的 读取不到 因为当前是事务ID是5 大于事务2的事务ID

数据结构(ReadView (可见性试图))

1.m_ids:表示在生成ReadView 时当前活跃的读写事务id列表

2.min_trx_id: 表示在生成 ReadView时当前活跃的读写事务ID列表 (m_ids)最小值

3.max_trx_id:生成ReadView时候应该分配给下一个事务的ID
4.creator_trx_id:生成ReadView时候的事务ID

判断事务可见性的规则

1.如果 trx_id 等于 creator_trx_id,说明当前事务在访问它自己修改过的记
录(本事务修改),所以这个版本可以被当前事务访问。
2.如果 trx_id 小于 min_trx_id,说明在Undo版本链中的这个事务在当前事
务生成 ReadView 前已经提交,所以这个版本可以被当前事务访问。
【当前事务在执行的时候, 这个快照已经生成了.】
3.如果 trx_id 大于或等于max_trx_id,说明在Undo版本链中的这个事务在
当前事务生成 ReadView后才开启,所以这个版本不可以被当前事务访
问。
4.如果 trx_id 在 min_trx_id 和 max_trx_id 之间,此时再判断一下 trx_id
是不是在 m_ids 列表中。
如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版
本不可以被访问;
如果不在,说明创建ReadView时生成该版本的事务已经被提交,该
版本可以被访

RR和RC的流程

Dingtalk_20220310111548.jpg

Undolog 链

事务和锁 - 图7
事务ID是更新的事务ID roll_pointer 指向了这条记录在undnlog 上一条修改的信息
TXR_ID (事务ID)
roll_pointer (指向本次修改之前的 undolog)

RC事务隔离级别实现图解

事务和锁 - 图8

事务和锁 - 图9

事务和锁 - 图10

LBCC(Lock Based Concurrency Controller)

锁的粒度

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

锁定力度

表锁>行锁

加锁效率

表锁>行锁

冲突概率

表锁>行锁

并发性能

表锁<行锁

锁的类型

插入意向锁

允许多个事务同时插入数据到同一个范围 比如数据范围是4-7 两个事务分别插入5,6 不会发生锁等待

自增锁

防止字段重复 数据插入以后就会释放不用等待事务提交之后在释放

共享锁

Dingtalk_20220310135050.jpg
select from user in share mode
select
from user where id =3 lock in share mode
读锁:获取一个数据行的读锁之后可以进行读操作 但是注意不要写 要么会造成死锁,多个事务可以共享一把读锁
作用:阻塞其他事务的修改 用在不允许其他事务修改的情况下

排他锁

Dingtalk_20220310135440.jpg
select * from user for update
用来操作数据的 也叫写锁 只要事务获取了这一行数据的排他锁 其他事务都不能修改或者读取
手工加锁: for update

意向锁

原因:
因为加表锁的时候需要去判断是否加了行锁 就需要遍历表里所有行 所以就需要加个意向锁就相当于有个标记
Dingtalk_20220310135952.jpg

我们给一行数据加上共享之后会默认加一个意向共享锁
给一行数据加排他锁之后会默认加意向排他锁

锁的原理

1.没有索引的情况下会锁表 没有索引会进行全表扫描 所以扫表
2.有索引的情况下锁住的是这行数据
3.如果没有聚簇索引会有个默认的rowid锁住
4.唯一索引给数据加锁 主键索引也会被锁住 因为在辅助索引需要先根据 name找到那一行id然后再通过id找到存放数据的地方

锁的算法(间隙锁和临键锁解决幻读问题)

记录锁

对于唯一性的索引(主键索引 和唯一索引) 使用等值查询的时候使用的就是记录锁

间隙锁(Gap Lock 锁定索引区间)

锁 索引的区间
查询的记录不存在 没有命中任何一个 record 无论用等值查询或范围查询都是用到的间隙锁

临键锁(next Key Lock 锁定索引区间包括行锁)

使用范围查询不只命中record 还包含了Gep 间隙
二级索引默认有一个 next Key Lock
Dingtalk_20220310142806.jpg
锁定大于18的所有区间
Dingtalk_20220310143105.jpg
如果是 =18
锁的是18这个区间和下一个区间

行锁(Record Lock)

二级索引 锁的是行锁 主键索引是 临键锁
select from table where id =1 for update 如果没有数据退化成 临键锁
select
from table where id >3 for update id是主键 所以对于> 3的数据加行锁

表锁

select * from table for update

唯一性索引 等值查询到一条记录的时候退化成 记录锁

没有匹配到任何记录退化成间隙锁

锁的流程

1.多个事务同时抢占的时候 加的是独占锁 抢占到锁的执行 其他事务等待
2.一个事务写操作的时候 其他事务可以读 读写不会互斥
3.共享锁和独占锁是互斥的
4.共享锁和共享锁不会互斥

事务隔离级别的选择

RR(可重复读)

RR 间隙锁 导致范围扩大
未使用到索引RR锁表

RC(读已提交)

未使用到索引RC锁行
半一致性可能会导致事务并发的问题

MVCC为什么 无法解决幻读

快照读

select from user where id=1
Read Committed隔离级别:每次select都生成一个快照读。
Read Repeatable隔离级别:*开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。

当前读

读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题
假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

死锁

相互竞争循环等待