mysql锁

midoll 482 2022-07-05

适合有一定基础的人看哈

LBCC

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

MVCC

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

  • 问题:这个快照什么时候创建?读取数据的时候,怎么保证能读取到这个快照而不
    是最新的数据?这个怎么实现呢?
    InnoDB 为每行记录都实现了两个隐藏字段:
    DB_TRX_ID,6 字节:插入或更新行的最后一个事务的事务ID,事务编号是自动递
    增的(我们把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事
    务ID)。
    DB_ROLL_PTR,7 字节:回滚指针(我们把它理解为删除版本号,数据被删除或记
    录为旧数据的时候,记录当前事务ID)。
    我们把这两个事务ID 理解为版本号。

共享锁

第一个行级别的锁就是我们在官网看到的Shared Locks (共享锁),我们获取了
一行数据的读锁以后,可以用来读取数据,所以它也叫做读锁,注意不要在加上了读锁
以后去写数据,不然的话可能会出现死锁的情况。而且多个事务可以共享一把读锁。

排它锁

第二个行级别的锁叫做Exclusive Locks(排它锁),它是用来操作数据的,所以又
叫做写锁。只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数
据的共享锁和排它锁。
排它锁的加锁方式有两种,第一种是自动加排他锁。我们在操作数据的时候,包括
增删改,都会默认加上一个排它锁。
还有一种是手工加锁,我们用一个FOR UPDATE 给一行数据加上一个排它锁,这个
无论是在我们的代码里面还是操作数据的工具里面,都比较常用。

  • 如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加
    上了共享锁。 因为给某些行加锁之后,会给表自动加意向锁

  • 如果一张表上面至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加
    上了排他锁。

  • InnoDB 的行锁,就是通过锁住索引来实现的。
    如果查询没有使用索引,会进行全表扫描,然后把每一个隐藏的聚集索引都锁住了。
    通过唯一索引给数据行加锁,主键索引也会被锁住
    clipboard(1)-1657010672156

记录锁、间隙(gap)锁、临键锁

clipboard(2)-1657010672152

  • where id = 4;等值查询,精确匹配到一条记录,此时使用的是记录锁;

  • where id = 6;where id >20; 查询记录不存在,没有命中record,此时使用的是间隙锁;

  • where id>=10; 命中record和gap间隙,此时使用临键锁,这是mysql默认的行锁算法,

  • 两种退化的情况:
    唯一性索引,等值查询匹配到一条记录的时候,退化成记录锁。
    没有匹配到任何记录的时候,退化成间隙锁。

  • 为什么要锁住下一个左开右闭的区间?——就是为了解决幻读的问题。
    一个事务前后两次读取数据数据不一致,是由于其他事务插入数据造成的,这种情
    况我们把它叫做幻读。

  • 不可重复读和幻读,的区别在那里呢?
    不可重复读是修改或者删除,幻读是插入。
    clipboard-1657010672087

隔离级别

Read Uncommited

RU 隔离级别:不加锁。

Serializable

Serializable 所有的select 语句都会被隐式的转化为select … in share mode,会
和update、delete 互斥。

Repeatable Read

RR 隔离级别下,普通的select 使用快照读(snapshot read),底层使用MVCC 来实
现。
在第一次创建ReadView后,这个ReadView就会一直维持到事务结束,也就是说,在事务执行期间可见性不会发生变化,从而实现了事务内的可重复读。
加锁的select(select … in share mode / select … for update) 以及更新操作
update, delete 等语句使用当前读(current read),底层使用记录锁、或者间隙锁、
临键锁。

Read Commited

RC 隔离级别下,普通的select 都是快照读,使用MVCC 实现。
在这一隔离级别下,可以在SQL级别做到一致性读,每次SQL语句都会产生新的ReadView。这就意味着两次查询之间有别的事务提交了,是可以读到不一致的数据的。
加锁的select 都使用记录锁,因为没有Gap Lock。

当不存在间隙锁的情况下,会有如下的场景:

master库有这么两个事务:
1、事务a先delete id<6,然后在commit前;
2、事务b直接insert id=3,并且完成commit;
3、事务a进行commit;
此时binlog记录的日志是:事务b先执行,事务a在执行(binlog记录的是commit顺序)
那么主库此时表里面有id=3的记录,但是从库是先插入再删除,从库里面是没有记录的。
这就导致了主从数据不一致。
为了解决这个bug,所以RR级别引入了间隙锁

RC 和RR 主要有几个区别:

1、RR 的间隙锁会导致锁定范围的扩大。
2、条件列未使用到索引,RR 锁表,RC 锁行。
3、RC 的“半一致性”(semi-consistent)读可以增加update 操作的并发性。


# mysql # 锁