, 看一个幻读的模型
表 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 这个记录已经提交了, 就能看到了
来源: http://www.bubuko.com/infodetail-2643540.html