美文网首页
数据库锁相关

数据库锁相关

作者: 瓢鳍小虾虎 | 来源:发表于2021-02-03 18:56 被阅读0次

    MVCC

    MVCC,即Multi-Version Concurrency Control, 多版本并发控制,是伴随着事务的需求而产生的设计思想,主要用来支持事务的并发操作、事务回滚等特性。

    innoDB的MVCC实现是基于undoLog,通过表记录的隐藏字段实现多版本控制。
    1)表隐藏字段:
    3部分,最后事务id(DB_TRX_ID)6字节,回滚段(DB_ROLL_PTR)7字节,递增记录id(DB_ROW_ID)6字节,再加上一个记录删除delete标记。
    2)undoLog
    insert类型的操作undoLog会记录undo_no,table id,数据唯一键的信息,事务id。
    update类型的操作undoLog会记录undo_no,table id,数据唯一键的信息,字段修改前的值,旧记录事务id,事务id。

    mysql的锁

    mysql的锁分为行级锁和表级锁。

    1. 行级锁:innoDB引擎实现。分为共享锁(读锁,S锁)和排它锁(写锁,X锁,独占锁)。

    共享锁的特点是多个事务可以同时加读锁,但是不能加写锁。

    显示加读锁:
    select ... lock in share mode;

    隐式加读锁:下面这种情况比较隐蔽,table_b会被加读锁,确切的说是间隙锁,所谓间隙锁就是锁住id为0到当前查询的最后一条记录的id的范围,不管这个范围中实际上有没有记录。由于select * 没有具体条件,这里innoDB会认为范围无限大,就变成table_b整个表都锁住了。
    insert into table_a select * from table_b;

    排他锁的特点是其他事务不能读也不能写。

    update/delete/select ... for update;

    关于间隙锁单记录锁

    单记录锁就是锁定一行数据的锁。
    innoDB对间隙锁的具体实现是使用了next-key锁
    next-key锁是单记录锁和间隙锁的组合用法,锁定的是一个范围。具体会锁住查询过程中所有遍历过的数据和数据之间的间隙
    next-key的查询规则具体会一直遍历找到需要查询的数据,如果查询条件是未加索引的字段,则会全表扫描,导致全表被被锁。
    select * from table_a where pay = 3 for update; // pay没加索引导致全表被加了锁
    特例
    1)使用唯一特性的字段查询不会使用间隙锁。插入的时候包含唯一字段也会转为单记录锁。
    2)如果事务隔离级别降级为读已提交(read commited,RC)则间隙锁失效。

    插入意向锁
    insert操作的时候加的锁。插入意向锁也是一种间隙锁,具体是指在插入之前先设置间隙锁
    一个典型的例子就是当前一个事务使用非主键作为条件update操作的时候,后一个事务insert会被阻塞,影响到了并发效率。如果两个事务都先不通过索引update然后insert就会出现死锁。

    总之,innoDB加锁默认都是使用next-key锁,锁定的是一个范围,如果sql写法不注意,就会锁住意料之外的行,影响到并发事务效率,甚至死锁。所以我们更新和查询都要尽量使用主键和索引。

    Predicate Lock
    因为next-key锁对多维空间列的支持不好,innoDB又提供了专门的Predicate Lock。大致思想就是通过锁查询条件而不是锁记录来达到目的。

    1. 表级锁:表级锁是由mysql服务实现的。

    读锁(LOCK_S锁):
    lock table tablename read;
    写锁(LOCK_X锁):
    lock table tablename write;

    通常上述方式一般不会在生产环境使用。因为加上去了除了使用写琐(手动释放),加了LOCK_X锁,表数据读写操作就全部禁止了。加表锁的时候也并不知道有没有行级锁的事务操作。

    为了解决这种场景,让表级锁和行级锁共存,mysql还特意提供了意向共享锁(IS)意向互斥锁(IX),类似于注册,这样加表锁的时候就不用全表扫描是否当前表有事务加行级锁了。

    意向共享锁(IS)
    事务想获得行级共享锁,需要先在表上加意向共享锁

    意向互斥锁(IX)
    事务想获得行级互斥锁,需要先在表上加意向互斥锁

    X(表写锁)S(表读锁)IX(意向互斥锁)IS(意向共享锁)

    表级加锁总的来看还是比较繁琐,表最好还是设计的时候索引字段什么的考虑的全面点。

    表的自增锁

    这是一种特殊的表级锁,自动模式(innoDB默认选项)中,自增序列在执行sql的时候就计算好了行数(如果是未知行数,则自动转为传统模式--加表级锁,insert结束后释放),然后释放掉,自增过程并不受事务控制,2个并发的事务中自增序列也会依次排列,不会出现冲突,但是如果前一个事务rollback,则那个事务加的数据对应自增序列就会丢掉,导致整个表的自增序列出现不连续。

    innoDB对事务模型的支持

    读未提交:直接读行记录,不管隐藏字段有什么信息。

    读已提交:读行记录,如果发现最新记录的事务id未提交,则根据回滚段找到undo日志,去读取上一个版本的数据。

    可重复读(mysql默认事务隔离级别):只关心自己事务id对应的数据,如果发现当前记录的事务版本不对,则通过回滚段找到undoLog去读取跟自己事务版本一致的数据。
    select读取的时候会先生成一个read-view快照,这个快照只存放当前事务id版本和以前版本的记录,事务过程中就算有别的事务update提交了,也不会影响到快照。通过这种方式实现了可重复读。

    快照只适用于select读的事务,如果是update操作则会实时读最新记录,从而数据结果集不一致,解决办法是先加间隙锁select...for update,这样事务结束之前其他事务想操作是成功不了的。通常幻读并不会影响数据update数据,所以我们通常可以忍受,这时候就要考虑一定要利用id或者索引,减少不必要的阻塞。

    串行化:串行化的原理其实还是利用了间隙锁,默认加了读锁,自然写锁就申请不到只能等着。所谓串行化并不是严格事务部分情况必须一个一个串行执行。

    几个案例

    1. 多事务并发insert导致的死锁现象

    1)3事务同时insert记录的时候,假设都包含一个唯一字段并且同名。

    2)因为唯一键,只会使用单记录锁,并且先加读锁去查询有没有,然后才能加写锁插入。

    3)第一个事务执行中途callback了,此时事务2和事务3都加了读锁(读锁不互斥),但是没办法加写锁,因为需互相等对方释放读锁,但是读锁的释放需要写操作完成即写锁释放。死锁产生。

    如果第一个事务成功提交,则不会出现死锁,因为后面的事务读操作就会发现唯一键冲突。
    mysql针对这种情况会主动让一个事务报错,退出事务,这时另一个事务就能操作成功。

    相关文章

      网友评论

          本文标题:数据库锁相关

          本文链接:https://www.haomeiwen.com/subject/rlrstltx.html