14.3.4 Phantom Rows 幻影行

14.3.4 Phantom Rows   幻影行

所谓的幻读为发生在一个事务 当相同的查询产生不同的结果集在不同的时间。

比如,如果一个SELECT被执行2次, 但是第2次返回的记录不是第一次返回的记录,


行是幻行


假设在child 表的id列上有一个索引 ,你需要读取和锁定所有的行 值大于100的,


以便更新选择行的一些列:


SELECT * FROM child WHERE id > 100 FOR UPDATE;


查询扫描索引从第一个记录id大于100开始, 让表包含记录90和102.

如果锁设置在index records 在扫描的范围 不堵塞插入 在区间上(在这个例子里,区间是90和102)

另外一个会话可以插入新值到表 id值为101.


如果你执行相同的查询 在相同的session,你会看到新的值id=101(一个幻读)


在查询返回的结果。 如果我们将一组行作为数据项, 新的幻读的child 会违背事务隔离原理,

一个事务应该在事务期间所读取的数据不会改变。


mysql> show variables like '%tx_isolation%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.01 sec)


 CREATE TABLE `child` (
  `sn` int(11) NOT NULL AUTO_INCREMENT,
  `id` int(11) DEFAULT NULL,
  `info` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`sn`)
);





mysql> show variables like '%tx_isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)


Session 1:
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
Empty set (0.00 sec)

                                                          Session 2:
                                                          mysql> select * from child;
                                                          +----+------+------+
                                                          | sn | id   | info |
                                                          +----+------+------+
                                                          |  1 |    1 | a1   |
                                                          |  2 |   99 | a99  |
                                                          +----+------+------+
                                                          2 rows in set (0.00 sec)

                                                         mysql> insert into child(id,info) 

values(101,'a101');
                                                        Query OK, 1 row affected (0.00 sec)

                                                    mysql> commit;
                                                   Query OK, 0 rows affected (0.01 sec)











继续测试:

Session 1:
mysql>  show variables like '%tx_isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

mysql>  SELECT * FROM child;
+----+------+------+
| sn | id   | info |
+----+------+------+
|  1 |    1 | a1   |
|  2 |   99 | a99  |
|  3 |  101 | a101 |
+----+------+------+
3 rows in set (0.00 sec)

mysql> insert into child(id,info) values(110,'a110');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from child;
+----+------+------+
| sn | id   | info |
+----+------+------+
|  1 |    1 | a1   |
|  2 |   99 | a99  |
|  3 |  101 | a101 |
|  4 |  110 | a110 |
+----+------+------+
4 rows in set (0.00 sec)

mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+----+------+------+
| sn | id   | info |
+----+------+------+
|  3 |  101 | a101 |
|  4 |  110 | a110 |
+----+------+------+
2 rows in set (0.00 sec)

                                                                            Session 2:

                                                                        mysql> select * from 

child;


                                                                      +----+------+------+
                                                                      | sn | id   | info |
                                                                      +----+------+------+
                                                                      |  1 |    1 | a1   |
                                                                      |  2 |   99 | a99  |
                                                                      |  3 |  101 | a101 |
                                                                      |  4 |  110 | a110 |
                                                                      +----+------+------+
                                                                     4 rows in set (0.00 sec)

                                                                    mysql> insert into child

(id,info) values(105,'a105');
                                                                    Query OK, 1 row affected 

(0.00 sec)

                                                                   mysql> commit;
                                                                   Query OK, 0 rows affected 

(0.01 sec)

                                                                  mysql> select * from child;
                                                                  +----+------+------+
                                                                  | sn | id   | info |
                                                                  +----+------+------+
                                                                  |  1 |    1 | a1   |
                                                                  |  2 |   99 | a99  |
                                                                  |  3 |  101 | a101 |
                                                                  |  4 |  110 | a110 |
                                                                  |  5 |  105 | a105 |
                                                                  +----+------+------+
                                                                  5 rows in set (0.00 sec)



为了防止幻读,InnoDB使用一个算法叫做 next-key locking   由 index-row locking和gap locking 组成。


InnoDB 执行 row-level locking 以这种方式 当它搜索或者扫描表的索引,


它设置共享锁或者排它锁 在它遇到的index records上.


因此, row-level locks 实际上是 index-record locks.

此外, a next-key lock  在一个Index record 也影响了 “gap” before that index record.


也就是说,一个next-key lock  是一个index record lock 加上一个gap lock 

在index record 之前的区间。


如果一个会话有一个共享锁或者排它锁在记录R上 在一个index里,


另外的会话不能立即插入到一个新的index record 在一个gap (在记录R之前)


当InnoDB  扫描一个Index, 它也可以lock gap 在最后一条记录后面 在index里,

比如之前的例子,防止任何插入到表 id值大于100的数据,


InnoDB设置锁包含一个lock区间 在id=102以后的区间



测试:
Session 1:

mysql> show variables like '%tx_isolation%';
+---------------+-----------------+
| Variable_name | Value           |
S+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)




Database changed
mysql> select * from child;
+----+------+------+
| sn | id   | info |
+----+------+------+
|  1 |   90 | a90  |
|  2 |  102 | a102 |
+----+------+------+
2 rows in set (0.00 sec)

mysql> select * from child where id>100 for update;
+----+------+------+
| sn | id   | info |
+----+------+------+
|  2 |  102 | a102 |
+----+------+------+
1 row in set (0.00 sec)

mysql> show index from child;
+-------+------------+------------+--------------+-------------+-----------+-------------

+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | 

Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------+--------------+-------------+-----------+-------------

+----------+--------+------+------------+---------+---------------+
| child |          0 | PRIMARY    |            1 | sn          | A         |           4 |     

NULL | NULL   |      | BTREE      |         |               |
| child |          0 | child_idx1 |            1 | id          | A         |           4 |     

NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------

+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)




Session 2:
mysql> insert into child(id,info) values(91,'a91');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(90,'a90');
ERROR 1062 (23000): Duplicate entry '90' for key 'child_idx1'
mysql> insert into child(id,info) values(89,'a89');
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into child(id,info) values(88,'a88');
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into child(id,info) values(92,'a92');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(93,'a93');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(94,'a94');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(95,'a95');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(96,'a96');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(97,'a97');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(98,'a98');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(99,'a99');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(100,'a100');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(101,'a101');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(102,'a102');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(103,'a103');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into child(id,info) values(104,'a104');


x∈[3,4]表示3≤x≤4 因为两端有等号,所以叫闭区间
x∈(3,4)表示3<x<4 因为两端没等号,所以叫开区间



锁了(90,无穷)

你可以使用 next-key locking 来实现一个唯一性检查 在你的应用里:


如果你读取你的数据 在共享模式,没有看到重复的 对于一条记录准备被插入,

然后你可以安全的插入你的记录,知道 next-key lock 设置在你的行的继承者 在读取阻止任何

同时插入一个重复的记录。因此,the next-key locking 让你可以lock 你表中不存在的东西

原文地址:https://www.cnblogs.com/zhaoyangjian724/p/6199837.html