zoukankan      html  css  js  c++  java
  • MYSQL隔离级别 与 锁

    1.四种隔离级别下数据不一致的情况

     
    脏读
    不可重复读
    幻读
    RU
    RC(快照读)
    RC(当前读)
    RR(快照读)
    RR(当前读)
    Serializable(串行化)
    ## 关于RR快照读时会不会造成幻读,我举一个例子,RR隔离级别,id主键
    ## 我也不知道这算不算幻读,
    事务A 事务B
    begin; begin;

    select count(*) from test;

    结果:1

     
      insert into test values(4,'asd')

    update test set name='zxcs';

     
    阻塞  
      commit

    select count(*) from test;

    结果:2

     

    那什么是当前读,什么是快照读呢?

    当前读:select * from table where ? lock in share mode;
        select * from table where ? for update;
        insert into table values (…);
        update table set ? where ?;
        delete from table where ?;
    快照读: select * from table where ?
     
    简单来说,当前读就是带S锁读或者带X锁读,或者dml操作,快照读:读不加锁,读写不冲突。
     
    快照读要从MVCC说起
     
    MVCC增加了一种可以不带锁的读取数据方式,但是读取的时版本快照,并不是最新内容
    快照读读取的就是MVCC中的版本快照
    MVCC只支持Mysql的InnoDB引擎中的已提交读(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别下使用

    MVCC的实现原理:----UNDO LOG + 隐藏字段trx_id 和roll_pointer

    ## trx_id : 对该记录最新修改的事务id

    ## roll_pointer:老版本号  --保存在undo log中

    ## 如果一个表有两个字段id和name ,实际上是这样的,会有两个是隐藏字段,其实应该是3个隐藏字段,还有一个跟MVCC无关。

    id
    name
    trx_id
    roll_pointer
    1 lxl 40 上一个版本记录的地址

    举个栗子,update table set name= 'lxlxlxl' where id=1;(id是主键),假如当先这条dml的事务号为41

    id name trx_id roll_pointer
    1 lxl 40 上一个版本记录的地址

    ### 这条记录会被放到undo log 中

    ### 然后

    id

    name trx_id roll_pointer
    1 lxlxlxl 41 trx_id=40的地址

    ###这条记录会放到表中

    那快照读的时候是怎么判断版本号的呢?

    ReadView中主要就是有个列表来存储我们系统中当前活跃着的读写事务,也就是begin了还未提交的事务。通过这个列表来判断记录的某个版本是否对当前事务可见。假设当前列表里的事务id为[80,100]。
    
    如果你要访问的记录版本的事务id为50,比当前列表最小的id80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
    
    如果你要访问的记录版本的事务id为85,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。
    
    如果不在那说明事务已经提交,所以版本可以被访问。如果你要访问的记录版本的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。
     
    这些记录都是去版本链里面找的,先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。
     
    版本链就是每条记录的隐藏字段roll_pointer组成的链表

    那么我有一个问题,当一个事务执行一个dml,是在commit后对表,还是在执行完dml后对表进行修改?

    我们可以来对比下RU 和 RC 隔离级别

    RU 是为什么会造成脏读的现象?就是为什么会读到未提交的数据?

    RC 为什么不会造成脏读?

    我们假设执行完dml 就会对表进行修改,而不是commit之后修改

    RU :id是主键

    事务A 事务B
    begin; begin
      update table set name= 'lxlxlxl' where id=1;
    select * from table  
    能读到事务B修改的数据  

    在RC,RR,RS中事务B需要commit 才能被其他事务看到

    可以说明

    执行完dml 就会对表进行修改,而不是commit之后修改

    再来看RC:id是主键

    事务A 事务B
    begin;  begin;  
      update table set name= 'lxlxlxl' where id=1;
    select * from table where id=1 lock in share mode  

    此时事务A阻塞,S锁和 事务B的X锁冲突

    此时事务B对表格已经修改,也成立

    但是因为锁冲突的原理不会被其他当前读的事务所看到

    如果这里采用快照读不会阻塞,也不会读到更改,因为只会读到上一个版本的记录

     
      commit;
    执行,并看到事务B的修改  
     
     
     
     
     
     
     
     
     
     
     
    ###我的意思是其他在执行完DML后就会对表格进行修改,也即会更改行记录的trx_id,但是在commit之后才会被发现,因为在commit之前,执行dml之后该记录带X锁,会与其他想这条记录的事务冲突,所以在这个期间不会被其他事务看到修改。
     
    2. 关于2pl

    2-PL  :Two-phase Locking ,锁操作分为两个阶段,加锁阶段和解锁阶段,并且保证加锁阶段与解锁阶段不相交,

               2PL就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁。

    关于2-pl 有以下变种:

    C2PL  : 在事物开始时对所有需要访问的数据获取锁。不存在死锁问题,要么事务等待不能开始,要么就已经得到了全部所需的锁

    S2PL  : 严格2PL,事务持有的写锁必须提交后再释放,读锁在阶段二时释放

    SS2PL:  强严格2PL,事务持有的所有锁必须在事务提交(完成)后释放;

    3. 分析一下 一个简单的update在不同隔离级别下的效果

    update test set name='lxl' where id=5

     id为主键id为二级唯一索引id为二级普通索引id不是索引
    RU

    在主键上id=5的记录加上X锁

    操作完成后释放所有X锁

    先在二级唯一索引上进行带锁的当前读(for update),

    找到id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁

    现在二级普通索引上进行待锁的当前读(for update),

    找到所有id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁,

    跟唯一索引的区别是:唯一索引只有一条记录

    使用半一致性读

    SQL走聚簇索引的全扫描进行过滤把每条记录都加上X锁,

    对于不满足where id=5的记录释放掉锁,

    最终只有符合条件的记录带上X锁,

    RC

    在主键上id=5的记录加上X锁

    操作完成后释放所有X锁

    先在二级唯一索引上进行带锁的当前读(for update),

    找到id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁

    现在二级普通索引上进行待锁的当前读(for update),

    找到所有id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁,

    (有人会说半一致性读,确实,半一致性读,能肯定的是,没执行update之前没有放锁,可以测试,但不能肯定他不合条件的是在获取所有锁->释放不合条件记录的X锁 -> 执行

    还是获取所有锁-> 执行->释放不合条件记录的X锁 ,下面拿例子说一下这个不一致性读)

    跟唯一索引的区别是:唯一索引只有一条记录

    使用半一致性读

    SQL走聚簇索引的全扫描进行过滤把每条记录都加上X锁,

    对于不满足where id=5的记录释放掉锁,

    最终只有符合条件的记录带上X锁,

    RR

    在主键上id=5的记录加上X锁

    操作完成后释放所有X锁

    先在二级唯一索引上进行带锁的当前读(for update),

    找到id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁

    例如 id 的二级索引上有值【1,2,5,7】

    现在二级普通索引上进行待锁的当前读(for update),

    找到所有id=5的记录后加上X锁,以及把【2-5】【5-5】【5-7】之间加上间隙锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁

    操作完成后释放所有X锁和gap锁,跟唯一索引的区别是唯一索引只有一条记录

     在聚集索引上扫表,

    并对每条记录加上X锁,

    但不会像RC那样把不符合条件的释放掉,

    直到事务结束,

    同时对每个间隙加上gap锁,

    例如有主键[1,2,3,4],

    在1-2  , 2-3,  3-4,4-~,所有间隙加上gap锁

    操作结束,把所有X锁和gap锁释放掉

    RS

    在主键上id=5的记录加上X锁

    操作完成后释放所有X锁

    先在二级唯一索引上进行带锁的当前读(for update),

    找到id=5的记录后加上X锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁,

    操作完成后释放所有X锁

    例如 id 的二级索引上有值【1,2,5,7】

    现在二级普通索引上进行待锁的当前读(for update),

    找到所有id=5的记录后加上X锁,以及把【2-5】【5-5】【5-7】之间加上间隙锁,

    然后通过主键值回到主键索引(聚集索引)中把对应的记录加上X锁

    操作完成后释放所有X锁和gap锁,跟唯一索引的区别是唯一索引只有一条记录

    在聚集索引上扫表,

    并对每条记录加上X锁,

    但不会像RC那样把不符合条件的释放掉,

    直到事务结束,

    同时对每个间隙加上gap锁,

    例如有主键[1,2,3,4],

    在1-2  , 2-3,  3-4,4-~,所有间隙加上gap锁

    操作结束,把所有X锁和gap锁释放掉

     

    结论:update test set name='lxl' where id=5

     

               (1)在id为主键的情况下,在RU,RC,RR,S隔离级别下加锁和释放锁的过程都是一样的

     

               (2)在id为二级唯一索引时,在RU,RC,RR,S隔离级别下加锁和释放锁的过程也是一样的

     

               (3)在id为二级普通索引时,在RU,RC下过程一样,在RR,S隔离级别下多了gap锁防止幻读

     

               (4)在id不是索引的时,RU隔离级别下:;

     

                                                        RC隔离级别下,SQL走聚簇索引的全扫描进行过滤把每条记录都加上X锁,对于不满足where id=5的记录释放掉锁,

     

                                                        RR隔离级别下,SQL走聚簇索引的全扫描对每条记录加上X锁,但不会像RC那样把不符合条件的释放掉,直到事务结束,符合2PL原则。同时                                                      对每个间隙加上gap锁

     

                                                        S隔离级别下,与RR级别一样,只不过强制把事务进行排序,不允许并发操作

    4.分析select ,update,delete 在RC级别下不命中索引的操作

    RC隔离级别,age不是索引
    select * from test where age=5;

    ## 分析:最后只有主键上age=5的记录带S锁

    ## 因为 S2PL的原因,读操作在读完可以把部分读锁释放不必等到commit再释放,而写锁必须事务提交才能释放

    update viptest.test set name='lxl' where age=5;

    ## 分析:最后只有主键上age=5的记录带X锁

    ## 因为半一致性读的原因,当读到有锁冲突的记录的时候,mysql会判断,如果不是update需要的数据,如果不是则跳过该记录。

    ## 如果读到没加锁的记录话,加锁和释放锁的操作不会被省略

    delete from viptest.test where age=5;

    ## 分析,假如主键上有age 【1,10】范围的数据,因为没走索引,【1,10】每个条记录加上X锁。

    ## 半一致性读只对update有效

     
  • 相关阅读:
    Swap Nodes in Pairs
    Search for a Range——稍微升级版的二分查找
    Set Matrix Zeroes——常数空间内完成
    Ubuntu系统---C++之Eclipse 开始工程项目
    Ubuntu系统---C++之Eclipse编译器 CDT插件安装
    Ubuntu系统---开机总会显示错误报告处理
    Ubuntu系统---C++之Eclipse IDE 编译器安装
    Ubuntu系统---终端下用g++进行c++项目
    Ubuntu系统---进行C++项目开发的工具
    YOLO---Darknet下的 GPU vs CPU 速度
  • 原文地址:https://www.cnblogs.com/start-from-zero/p/12580549.html
Copyright © 2011-2022 走看看