mysql-innodb 2锁与事务

大纲:

  1. 事务特性
  2. 事务隔离级别
  3. 日志
  4. 死锁
  5. 事务隔离性实现

 

一、事务特性

  1. 原子性(Atomicity):对数据的修改要么全部执行成功,要么全部失败
  2. 一致性(Consistent):数据一致性,其他三点就是为了保证数据最终一一致性
  3. 隔离性(Isalotion):事务之间相互隔离,不受影响,这个与事务设置的隔离级别有密切的关系
  4. 持久性(Durable):一个事务提交后,这个事务的状态会被持久化到数据库中

 

二、事务隔离级别

  1. 读未提交(READ UNCOMMITTED):可读到未提交的数据(脏读)
  2. 读已提交(READ COMMITTED):读所有已提交事务的数据(不可重复读)
  3. 可重复读(REPEATABLE READ):每次事务中读到的数据保持一致(幻读指可重复读隔离级别下,同一事务中,相同查询范围,第二次读到第一次未读到的行(新插入的行),幻读仅在当前读情况下出现,INNODB中用间隙锁规避幻读)
  4. 串行化(SERIALIZABLE):有冲突的事务串行执行

1和4不可在生产应用,RC和RR应用场景不同,RC和RR快照读都用MVCC做并发控制,但MVCC开启READ VIEW时机不同。

 

三、日志

逻辑日志简单理解是“id=2这一行c字段更新成了3”,物理日志是数据页的改动

 

undolog:

逻辑日志,innodb存储引擎层记录,作用是事务回滚和帮助mvcc生成版本链

undolog为本次事务中每一个写入操作记录一个逆向操作,事务回滚时可以通过undolog把buffer pool中的脏页修改回去。

 

binlog:

逻辑日志,mysql的server层记录,作用是:1.主从同步和数据恢复,2.扩容(全量备份+binlog重放),有些系统也可以通过解析binlog日志,做一些其他工作,例如将binlog中解析出的数据写入es。

追加记录

 

redolog:

物理日志,innodb存储引擎层记录,作用是数据库异常重启后,已经提交的事务不会丢失,这个能力称为crash-safe

redolog由多个文件组成,循环记录,可以通过参数配置每个文件大小和文件数量

下图中,redolog有4个文件组成,write pos是写入位置,write pos到checkpoint之前的是已经刷入磁盘的日志,已经可以写入新的内容,当write pos赶上checkpoint的时候,需要停止写redolog,把checkpoint到write pos之间关联的脏页flush到硬盘,再继续记录redolog

 binlog与redolog:

  1. 都需要持久化,他们写入时机遵循两阶段提交,下图中的commit不是事务的commit操作而是redolog的提交状态
  2. 崩溃恢复的时候用redolog恢复已提交事务数据的时候,状态为commit的直接恢复,prepare状态有两种情况:1.binlog中没有记录mysql就崩溃了,binlog中还没有记录,这种情况不恢复,2.binlog中有记录,没有commit崩溃了,这种情况恢复。ps:redolog和binlog都记录了事务id,所以prepare状态的redolog可以在binlog中关联到。
  3. binlog和redolog为什么都需要:binlog是sever层的日志,可以夸存储引擎使用,没有崩溃恢复能力,写入binlog的日志并不一定已经将数据写入硬盘中;redolog是存储引擎层日志,innodb记录redolog后返回事务提交成功,但还没有写入磁盘,这时mysql崩溃重启,innodb依然可以根据redolog恢复内存数据,并写入磁盘。

 

 

 

四、锁

按性质分:共享锁(读锁),SELECT ...LOCK IN SHARE MODE,排他锁(写锁)UPDATE,DELETE,INSERT。写写互斥、读写互斥、读读相容

按粒度分:行锁、表锁、间隙锁、临键锁(next-key lock)

行锁-行记录在一个事务中写操作上行锁,其他事务不可修改改行

表锁-innodb中的表锁一般指MDL锁,MDL写锁会在ALTER TABLE时加上,MDL读锁会在SELECT,UPDATE时加上,写写,读写操作互斥。因此,修改表结构注意2点:1.如果热点表,修改表会产生大量阻塞,一定要在表访问少的时候修改表结构以免cpu飙高。2.修改表的时有有大事务,导致修改表线程抢不到MDL锁,也会导致其他线程阻塞,可以加上ALTER TABLE TAB_NAME WAIT N ADD COLOMN...可以在几秒内抢不到MDL写锁后放弃本次ALTER操作。

间隙锁与临键锁-以下记录产生6个间隙:(-∞,5)(5,10)(10,15)(15,20)(20,25)(25,+supernum),在间隙上加锁称为间隙锁,不允许在这个间隙新加入数据;每个间隙锁加上一个行锁,区间前开后闭称为临键锁:(-∞,5](5,10](10,1where5](15,20](20,25](25,+supernum];

id(主键)c(普通索引)d(无索引)
5 5 5
10 10 10
15 15 15
20 20 20
25 25 25

RR级别默认开启间隙锁,RC级别没有间隙锁 

RR级别innodb加锁原则:

1.加锁的基本单位是临键锁

2.锁是加在索引上的,只有访问到的

3.索引上的等值查询,给唯一索引加锁的时候,临键锁退化为行锁

4.索引上的等值查询,遍历到第一个不满足条件的值为止,临键锁退化为间隙锁

5.唯一索引上的范围查询,遍历到第一个不满足条件的值为止,例如主键id>10 and id<=15的情况,(15,下一个主键值]这个区间也会被锁上。(这条的逻辑时比较奇怪的)

 

五、死锁

1.行锁在访问到行资源时时候就会被加上,根据两阶段提交协议,在事务提交时释放,当两个事务相互持有对方事务需要的行锁,并等待获取对方已经持有的行锁时,就形成了死锁。

如何避免:

  1. 将容易形成行锁争抢的语句尽量写在事务最后,减少持有行锁的时间。
  2. 存储引擎本身拥有死锁检查,当线程出现行锁出现争抢的时候,存储引擎就会检查死锁,如果有死锁将回滚其中一个事务。因此尽量从业务上减少行锁间的争抢。例:账务系统更新一个商户的金额,在表里商户用一行数据表示该商户,可以在设计的时候将这一行数据映射到十行上,如id1到id10都表示某一个商户,这样更新该商户金额的时候,将减少行锁争抢。

 

 2.在RR隔离级别下,next-key-lock时分2步加上的(间隙锁+行锁),c上有索引的情况下,SESSION B获取锁的时候,间隙锁其实获取成功的,获取行锁时block,因此在SESSION Ainsert的时候,会出现死锁。

 

 

 

六、事务隔离性实现

LBCC(Lock-Based Concurrent Control):当前读

update,delete写操作时候先读后写,读到行数据最新提交的版本并加行锁至事务提交。

 

MVCC(Multi-Version Concurremt Control):快照读

RC与RR两种隔离级别下,利用table view和undo log对select形成快照 ;整库备份的时候也是开启一个事务,然后所有的数据形成快照再备份。

InnoDB行记录中维护了三个隐藏列:1rowid行号,2trx_id(每个事务一个唯一标识)表示事务开启先后顺序 ,3db_roll_ptr回滚指针,回滚指针会指向undolog链表,找到该行的历史版本链

table view中保存四个数据:min_trx_id当前活跃事务的最小id,max_trx_id当前最大id,trx_ids为活跃事务id列表,creator_trx_id当前事务id

根据table view判断版本链中那个版本可用过程:

  1. 行数据中trx_id==creator_trx_id,当前版本修改的数据(可见)
  2. 行数据中trx_id<min_trx_id,已经提交过的数据(可见)
  3. 行数据中trx_id>max_trx_id,生成table view后开启的事务(不可见)
  4. 行数据中trx_id在min_trx_id-max_trx_id之间,如果在trx_ids中说明未提交(不可见),不在trx_ids中说明以提及(可见)
  5. 不可见的版本通过版本链找到上一个版本重复上面的过程

 

posted @ 2021-06-06 17:21  扶不起的刘阿斗  阅读(79)  评论(0编辑  收藏  举报