zoukankan      html  css  js  c++  java
  • Mysql事务隔离性与相关锁的总结

    事务隔离级别

    隔离级别 脏读 不可重复读 幻读
    读未提交 可以出现 可以出现 可以出现
    读提交 不允许出现 可以出现 可以出现
    可重复读 不允许出现 不允许出现 可以出现
    序列化 不允许出现 不允许出现 不允许出现

    注意点

    上述隔离级别都是这么定义的,但是InnoDB和Falcon存储引擎已经通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了幻读问题。

    行锁的类型

    InnoDB 实现了两种类型的行级锁:

    • 共享锁

      (也称为 S 锁):允许事务读取一行数据。

      可以使用 SQL 语句 select * from tableName where … lock in share mode; 手动加 S 锁。

    • 独占锁

      (也称为 X 锁):允许事务删除或更新一行数据。

      可以使用 SQL 语句 select * from tableName where … for update; 手动加 X 锁。

    S 锁和 S 锁是兼容的,X 锁和其它锁都不兼容,举个例子,事务 T1 获取了一个行 r1 的 S 锁,另外事务 T2 可以立即获得行 r1 的 S 锁,此时 T1 和 T2 共同获得行 r1 的 S 锁,此种情况称为锁兼容,但是另外一个事务 T2 此时如果想获得行 r1 的 X 锁,则必须等待 T1 对行 r 锁的释放,此种情况也成为锁冲突

    为了实现多粒度的锁机制,InnoDB 还有两种内部使用的意向锁,由 InnoDB 自动添加,且都是表级别的锁。

    • 意向共享锁(IS):事务即将给表中的各个行设置共享锁,事务给数据行加 S 锁前必须获得该表的 IS 锁。
    • 意向排他锁(IX):事务即将给表中的各个行设置排他锁,事务给数据行加 X 锁前必须获得该表 IX 锁。

    意向锁的主要目的是为了使得行锁表锁共存。表 2 列出了行级锁和表级意向锁的兼容性。

    行级锁和表级意向锁的兼容性

    锁类型 X IX S IS
    X 冲突 冲突 冲突 冲突
    IX 冲突 兼容 冲突 兼容
    S 冲突 冲突 兼容 兼容
    IS 冲突 兼容 兼容 兼容

    行锁的算法

    读锁和写锁都是行级锁,InnoDB的行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁,InnoDB行锁分为3中情形:

    1. 记录锁(Record Lock):对索引项加锁。
    2. 间隙锁(Gap Lock):对索引项之间的“间隙”、第一条记录前的“间隙”或最后一条记录后的“间隙”加锁。
    3. 临键锁(Next-key Lock):前两种的结合,对记录及其前面的间隙加锁。

    InnoDB这种行锁的实现特点意味着,如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟锁表一样

    一致性非锁定读和多版本并发控制

    一致性非锁定读(consistent nonlocking read)是指InnoDB通过行多版本控制(Multi Version Concurrency Control, MVCC)的方法来读取当前执行时间数据库中行的数据。

    即如果读取的行正在执行变更操作,这时读取不会等待行锁的释放,而是会读取行的一个快照数据。快照是指该行的一个历史数据,通过undo操作来完成。这种方式极大提高了数据库的并发性,这也是InnoDB的默认设置。

    快照是当前行的一个历史版本,但可能存在多个版本,行数据存在多个快照数据,这种技术成为行多版本技术,由此带来的并发控制,称为多版本并发控制(MVCC)。InnoDB在READ COMMITED 和 REPEATABLE READ隔离级别时,会使用非锁定的一致性读,但是在这两种隔离级别使用的快照数据定义却不同:

    • READ COMMITED: 总是读取最新一份快照
    • REPEATABLE READ: 总是读取事务开始时的行数据版本

    我们执行一个示例:

    一致性非锁定读
    时间 会话A 会话B
    1 BEGIN
    2 select * from z where a = 3;
    3 BEGIN
    4 update z set b=2 where a=3;
    5 select * from z where a = 3;
    6 COMMIT;
    7 select * from z where a = 3;
    8 COMMIT;

    在这个例子中我们可以清晰的看到0、1、2三种隔离级别的区别:

    #在事务开始前我们可以分别调整为0、1、2三种隔离级别,来查看不同的输出
    mysql> set session transaction isolation level READ UNCOMMITTED;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select @@tx_isolation;
    +------------------+
    | @@tx_isolation   |
    +------------------+
    | READ-UNCOMMITTED |
    +------------------+
    1 row in set (0.00 sec)
    
    # A会话:T1事务
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from z where a = 3;
    +---+------+
    | a | b    |
    +---+------+
    | 3 |    1 |
    +---+------+
    1 row in set (0.00 sec)
    
    # B会话:T2事务
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> update z set b=2 where a=3;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    # A会话:T1事务,如果此时隔离级别是READ-UNCOMMITTED,因为此刻事务2可能会回滚,所以出现了脏读
    mysql> select * from z where a=3;
    +---+------+
    | a | b    |
    +---+------+
    | 3 |    2 |
    +---+------+
    1 row in set (0.00 sec)
    
    # A会话:T1事务,如果此时隔离级别是大于READ-UNCOMMITTED的更高级别
    mysql> select * from z where a=3;
    +---+------+
    | a | b    |
    +---+------+
    | 3 |    1 |
    +---+------+
    1 row in set (0.00 sec)
    
    # B会话:T2事务
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
    
    # A会话:T1事务,如果此时隔离级别是READ-COMMITTED,因为数据和事务开始时读取的出现了不一致,因此称为不可重复读,能够读到其他事务的结果,违反了事务的隔离性
    mysql> select * from z where a=3;
    +---+------+
    | a | b    |
    +---+------+
    | 3 |    2 |
    +---+------+
    1 row in set (0.00 sec)
    
    # A会话:T1事务,如果此时隔离级别是大于READ-COMMITTED的更高级别
    mysql> select * from z where a=3;
    +---+------+
    | a | b    |
    +---+------+
    | 3 |    1 |
    +---+------+
    1 row in set (0.00 sec)
    
    # A会话:T1事务
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
    

    总结

    未提交读 ReadUnCommited 就是读写都不加任何锁

    已提交读 ReadCommitted 读取 不加锁 写入 加行锁(如果按索引过滤,那么只对筛选数据进行加行锁,但是如果不是按索引进行过滤,就对所有记录加行锁(但是Mysql对此进行了优化,会将不满足条件的记录释放锁)),可以防止更新丢失。

    可重复读 RepeatableRead 就是多次读取数据,结果都一样,如果是已提交读,事务过程中,如果有其他事务commited了查询范围内的数据,那么是可以读取到新的写入操作的,但是可重复读就防止了这种情况。就InnoDB引擎来说,通过多版本并发控制(MVVC,只使用在已提交读,可重复读两种事务隔离级别中)可以实现一致性读取,通过临键锁(行锁,间隙锁的组合)的方式可以防止其他事务对相关数据的写入。这里主要是该事务使用中的行,以及相关间隙数据,对这些数据行加锁,其他事务不可以新增,删除,修改。

    序列化 SERIALIZABLE 就是每一个读取都会加读锁,写入就添加写锁

    参考

    MySQL 事务隔离级别和锁

    MySQL的锁和事务隔离级别

    理解 MySQL 一致性非锁定读原理

    秒懂InnoDB的锁

    MySQL 事务的隔离级别与锁

    B站视频教程

    mysql SERIALIZABLE隔离级别死锁问题

  • 相关阅读:
    c coroutine
    leveldb(ssdb)性能、使用场景评估
    [微信协议分析] 多媒体
    [微信协议分析] 多点登陆
    [微信协议分析] 文本消息
    paxos(chubby) vs zab(Zookeeper)
    分布式一致性算法
    erlang 健壮性
    tcp 出现rst情况整理
    tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项
  • 原文地址:https://www.cnblogs.com/hongdada/p/12230580.html
Copyright © 2011-2022 走看看