zoukankan      html  css  js  c++  java
  • 一个幻读模型引出的记录可见性判断

    Ⅰ、看一个幻读的模型

    表a中有4条记录1,3,5,7,开两个session

    session1:begin;    delete <= 7 ;
    session2:begin;    insert a values(2);    commit;
    session1:commit;
    

    这个模型在rc的情况下,这是没问题的,只是加记录锁,不会锁范围,插入2是可以的。最后a上就剩2这个记录

    那binlog里面记录的内容就有讲究了,假设是statment格式的binlog

    insert 2;
    delete <= 7;
    

    这时候数据库做同步,从库上就gg了,一个事务所做的修改对另一个事务不可见,好似串行

    这就是不符合隔离性的要求,并行执行和串行执行的结果不一样

    row格式的binlog是下面这样记录的:

    insert 2;
    delete 1,delete 3, delete 5, delete 7
    

    这样2就还在

    所以,一定要用row,row记录的是一条一条记录,而不是简单的sql.所以说rc的情况下设置为row,主从还是可以保持一致的

    5.1版本才支持row,主从复制从3.23就开始支持,中间差了4.0,4.1,5.0三个版本,最早为什么innodb要支持这样的锁,也有部分原因是当时的MySQL的复制不用Next-key Lock,就是根本不可用的,因为innodb可以并行的,有并发问题,不像其他存储引擎有表锁

    tips:

    • binlog_format这个参数5.7之前默认statement,之后默认row
    • 其实不用我们担心,在事务隔离级别为rc的情况下,binlog_format随便你怎么设MySQL都会给你记为row的,不信可以试试

    Ⅱ、如何判断一条记录的可见性

    先讲两句题外话,这个判断的是根据事务id来做的,每条记录有rowid、txid、rowpointer,后面才接用户的列

    txid是6个字节的,其实每个事务分配的,而且是全局自增的,在共享表空间的某个位置存放着当前max的txid,所以每开启一个事务都会分配一个txid

    2.1 具体怎么判断?

    ①当前活跃事务列表,里面记录着当前正在执行未提交的事务

    begin;
    xxx    随便什么语句
    

    这时候产生一个read_view的内存对象,现在在mysql里面完全看不到,能看到的就是下面这个,有多少个read_view开着,我们通过这个read_view来判断记录的可见性

    --------------
    ROW OPERATIONS
    --------------
    0 queries inside InnoDB, 0 queries in queue
    0 read views open inside InnoDB
    Process ID=23137, Main thread ID=139830599059200, state: sleeping
    Number of rows inserted 116, updated 27, deleted 0, read 130
    0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
    

    这个read_view就是把事务开始的时候事务活跃列表拷贝一份出来,这时候会拿到很多个事务id,只要你的记录对应的事务id在这个活跃列表中,意味着这个记录不可见,因为这条记录在select这个事务开启的时候还没提交,所以该记录的事务id对新产生的事务是不可见的

    ②read_view在rc和rr中的区别
    rr中read_view只产生一次,rc中每执行一条sql语句就会创建一个read_view

    原因:rc可以读到已经提交的事务的记录,第二次执行还要检查事务活跃列表,如果提交了这条记录就可见,而rr实现了可重复读

    如果一个事务执行时间很长,它插入了一条记录,是否可见?就看创建read_view的时候这个tx_id有没有在活跃列表中,如果不在就意味着不可见,那就不会删掉了。

    rr的好处,read_view只创建一次,rc要创建n次

    如果事务活跃列表很长的话,每次拷贝的时候要锁住所有活跃事务(latch,5.5版本叫kernel_mutex,这个锁很大),需要时间还是挺长的,但5.6,5.7都开始做优化了(把大锁拆分了)

    所以,一个事务中有很多条操作,而且全是select操作(随机主键值查询),这时候rr性能更好,没有insert所以不会有gap锁导致的并发插入影响,这种情况太少了,所以我们还是选择rc

    2.2 read_view什么时候分配?

    begin的时候还是执行第一条sql的时候?

    测一把便知

    mysql> select @@tx_isolation;
    +-----------------+
    | @@tx_isolation  |
    +-----------------+
    | REPEATABLE-READ |
    +-----------------+
    1 row in set (0.00 sec)
    
    mysql> desc l;
    +-------+---------+------+-----+---------+-------+
    | Field | Type    | Null | Key | Default | Extra |
    +-------+---------+------+-----+---------+-------+
    | a     | int(11) | NO   | PRI | NULL    |       |
    +-------+---------+------+-----+---------+-------+
    1 row in set (0.00 sec)
    
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    +---+
    3 rows in set (0.00 sec)
    

    测试一:

    session1:
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    session2:
    mysql> begin;                 
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into l values (4);
    Query OK, 1 row affected (0.00 sec)
    
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    | 4 |
    +---+
    4 rows in set (0.00 sec)
    
    session1:
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    | 4 |
    +---+
    4 rows in set (0.00 sec)
    

    测试二:

    session1:
    mysql>
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    +---+
    3 rows in set (0.00 sec)
    
    session2:
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into l values (4);
    Query OK, 1 row affected (0.00 sec)
    
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    | 4 |
    +---+
    4 rows in set (0.00 sec)
    
    session1:
    mysql> select * from l;
    +---+
    | a |
    +---+
    | 1 |
    | 2 |
    | 3 |
    +---+
    3 rows in set (0.00 sec)
    

    按道理如果事务隔离级别为rr,那一个事务提交了,对另一个事务不可见,解决不可重复读,这样看测试二是合理的,
    那为什么,测试二session1一开始select了一把,session2里面事务提交了,session1就不可见,而测试一session1一开始没有select,后面再select就可见了?这是重复读的体现吗?

    原因:rr隔离级别的时,事务中有select时会创建一个read_view,而且一个事务只创建一次,所以测试一的时候,最后session1 select的时候才创建了read_view,发现session2的事务中相关记录已经commit了,不在事务活跃列表中,所以读到了这条记录,而测试二,session1 开启事务,第一个select的时候就创建了read_view,这时候session2里面的事务还没开启,第二个select的时候用的还是原来的rv,这样就不可见了

    tips:
    如果希望begin的时候就创建read_view

    必须用start transaction with consistent snapshot; 结合rr用,因为rc时候,事务中每执行一个sql就会创建read_view

    session1:
    start transaction with consistent snapshot;    创建了rv
    
    session2:
    begin;
    insert aaa;
    commit;
    
    session1:
    select aaa;查不到,创建rv的时候,session2中的事务还不存在
    
    如果rc的话能读到aaa,因为第三步session1里执行select又会创建一个rv,会发现aaa这个记录已经提交了,就能看到了
    
  • 相关阅读:
    如何使标签a处于不可用状态
    document.referrer的使用和window.opener 跟 window.parent 的区别
    纯CSS让overflow:auto页面滚动条出现时不跳动
    闭包的使用实例
    VMware workstation使用小技巧
    个人命令简记
    中国剩余定理
    UVA 10603 倒水问题
    Haybale Stacking(差分数组 + 求中位数的一些方法 + nth_element)
    POJ 1511 Invitation Cards (最短路的两种方法spfa, Dij)
  • 原文地址:https://www.cnblogs.com/---wunian/p/9181095.html
Copyright © 2011-2022 走看看