0.MySQL并发事务访问相同记录

0.1 情景分类

情景 造成问题 如何处理
读-读 允许并发事务读
写-写 脏写 锁,排队执行
读-写 or 写-读 脏读,幻读,不可重复读 MVCC

0.1.1 写-写

在这种情况下会发生 脏写 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务 相继对一条记录做改动时,需要让它们 排队执行 ,这个排队的过程其实是通过 锁 来实现的。这个所谓 的锁其实是一个 内存中的结构 ,在事务执行前本来是没有锁的,也就是说一开始是没有 锁结构 和记录进 行关联的,如图所示:
image.png

当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的 锁结构 ,当没有的时候 就会在内存中生成一个 锁结构 与之关联。比如,事务 T1 要对这条记录做改动,就需要生成一个 锁结构 与之关联:
image.png
image.png
image.png

  • 不加锁 意思就是不需要在内存中生成对应的 锁结构 ,可以直接执行操作。
  • 获取锁成功,或者加锁成功 意思就是在内存中生成了对应的 锁结构 ,而且锁结构的 is_waiting 属性为 false ,也就是事务 可以继续执行操作。
  • 获取锁失败,或者加锁失败,或者没有获取到锁 意思就是在内存中生成了对应的 锁结构 ,不过锁结构的 is_waiting 属性为 true ,也就是事务 需要等待,不可以继续执行操作。

    0.2 并发问题的解决方案

    0.2.1 MVCC

    读操作利用多版本并发控制( MVCC ),写操作进行 加锁

普通的SELECT语句在READ COMMITTED(读已提交)和REPEATABLE READ(可重复读)隔离级别下会使用到MVCC读取记录。

  • 在 READ COMMITTED 隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一 个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改 ,也就 是避免了脏读现象;
  • 在 REPEATABLE READ 隔离级别下,一个事务在执行过程中只有第一次执行select操作才会生成一个ReadView,之后的 select 操作都是复用这个ReadView,这样也就避免了不可重复读和幻读的问题。

    0.2.2 读写加锁

    image.png

    1.锁的分类

    MySQL 锁机制 - 图6
    image.png


1.1 按照锁的粒度

1.1.1 表锁

1.1.1.1 表级别的S/X锁

1.1.1.2 意向锁

  1. InnoDB支持多粒度锁,它允许行级锁与表级锁共存,而意向锁就是其中的一种表锁。
  2. 1、意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁和行锁)的锁共存。
  3. 2、允许行级锁与表级锁共存,而意向锁就是其中的一种表锁。
  4. 3、表明“某个事务正在某些行持有了锁 该事务准备去持有锁”。

意向共享锁(Intention Shared Lock)

意向共享锁(IS):表示事务准备(有意向)给数据行加入共享锁(S锁),也就是说一个数据行加共享锁前必须先取得该表的IS锁

  1. -- 事务要获取某些行的S锁,必须先获的表的IS
  2. select * from table ... LOCK IN SHARE MODE;

意向排他锁(Exclusive Lock)

意向排他锁(IX):类似上面,表示事务准备(有意向)给数据行加入排他锁(X锁),说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

  1. -- 事务要获取某些行的X锁,必须先活得表的IX
  2. select * from table ...FOR UPDATE;

InnoDB支持多种锁,特定情境下,行锁可以与表级锁共存。
意向锁是由存储引擎自己维护的,用户无法手动操作意向锁,在为数据行添加共享/排他锁之前,InooDB会先获取该数据行 所在数据表的对应意向锁

意向锁兼容互斥性

1. 意向锁与意向锁的兼容互斥性
意向共享锁(IS) 意向互斥锁(IX)
意向共享锁(IS) 兼容 兼容
意向互斥锁(IX) 兼容 兼容

2.意向锁与排他锁/共享锁的兼容互斥性

注意这里的排他/共享锁指的是表锁,意向锁不会与行级别的共享/排他锁互斥。

意向共享锁(IS) 意向互斥锁(IX)
共享锁(S) 兼容 互斥
互斥锁(X) 互斥 互斥

意向锁目的
  • 意向锁的存在是为了协调行锁和表锁的关系,支持多粒度的锁并存。
  • 意向锁是一种不与行级锁冲突的表级锁。
  • 表明“某个事务正在某行持有了锁或该事务准备去持有锁”。

image.png

1.1.1.3 自增锁(AUTO_INCREMENT)

AUTO_INC 锁是当向

1.1.1.4 元数据锁

Meta Data Lock ,MDL 属于表锁范畴。MDL 的作用是 保证读写的正确性。
比如一个查询正在遍历一个表中的数据,而执行期间另外一个线程对这个表结构做变更,增加了一列,那么查询线程拿到的结果跟表结构对不上,肯定不行。

因此,当对一个表做增删改查的时候,加MDL 读锁;当要对表做结构变更操作的时候,加MDL写锁。

1.1.2 行锁

1.1.2.1 记录锁(Record Lock)

记录锁是仅仅把一条记录锁上。
如图所示,仅仅是锁住了id为8 的记录,对周围的数据没有影响。
image.png
记录锁是有S锁和X锁之分的,称之为S型记录锁和X型记录锁。

  • 当一个事务获取了一条记录的S记录锁后,其他事务也可以继续获取该记录的S记录锁,但是不可以获取X锁。
  • 当一个事务获取了一条记录的X锁后,其他事务既不可以获取S锁,也不可以继续获取X锁。

1.1.2.2 间隙锁(Gap Lock)

1.1.2.3 临键锁(Next-Key Locks)

1.1.2.4 插入意向锁(Insert Intention Locks)

1.1.3 页锁

2.间隙锁实践

2.1 主键索引-间隙锁

1.数据准备

image.png

2.事务A中开启区间排他锁

for update 开启排他锁(X锁)。
image.png

3.事务B中插入区间内数据

很明显被阻塞了。
image.png

2.2 主键索引-死锁

image.png

2.2.1 准备数据

image.png

2.2.2 事务A删除一个不存在的值 获取间隙锁

  1. delete from student where id=4;
  2. 获取间隙锁(1,5]

2.2.3 事务B删除一个不存在的值 获取间隙锁

  1. delete
  2. from student
  3. where id = 3;
  4. 获取间隙锁(1,5]

2.2.4 事务A 要往间隙内插入数据

image.png
由于事务B已获取到间隙锁,此时事务A插入阻塞

2.2.5 事务B 要往间隙内插入数据

出现死锁
image.png

2.3 普通索引-间隙锁

image.png

2.3.1 准备数据

image.png

2.3.2 事务A删除一个不存在的值获取到间隙锁

image.png
不存在的值就会获取到向上及向下最近的间隙锁。
获取到(12,25] 范围的间隙锁

2.3.3 事务B要往间隙锁内插入数据

说明被阻塞
image.png

2.4 普通索引-死锁

  • 在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
  • 在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通普通索引排序,再根据唯一索引排序。
  • 普通索引如果删除不存在的值key,则会在最左 num[i] key 充当间隙锁。
  • 普通索引如果删除索引所在的值num[i],则会在[num[i-1],nums[i+1]]之间建立间隙锁,很容易两个事务之间出现死锁情况。

    2.4.1 准备数据

    image.png
    id 主键索引
    age 普通索引

    2.4.2 事务A删除存在的一个普通索引所对应数据

    image.png

产生间隙锁(8,25]

2.4.3 事务B删除存在的一个普通索引所对应数据

image.png
产生间隙锁(3,12]

2.4.4 事务A创建普通索引在交际区间内数据

  1. insert into student value(12,10);
  2. 此时被阻塞。

2.4.5 事务B 创建普通索引在交际区间内数据

  1. insert into student value (6, 9);
  2. 直接爆死锁。

3.加锁规则

3.1 唯一索引等值查询

  • 当索引项存在时,next-key lock 退化为 record lock;
  • 当索引项不存在时,默认 next-key lock,访问到不满足条件的第一个值后next-key lock退化成gap lock

3.2 唯一索引范围查询

默认 next-key lock,(特殊’<=’ 范围查询直到访问不满足条件的第一个值为止)

引用

https://www.cnblogs.com/shoshana-kong/p/14109826.html