Phantom reading of InnoDB (2)

The isolation level of MySQL InnoDB transactions has four levels, the default is "repeatable read" (REPEATABLE READ).

  • READ UNCOMMITTED. Another transaction modifies the data, but has not yet committed, and the SELECT in this transaction will read the uncommitted data (dirty read).
  • READ COMMITTED. This transaction reads the latest data (after other transactions are committed). The problem is that in the same transaction, the same SELECT before and after will read different results (not repeated read).
  • REPEATABLE READ. In the same transaction, the result of SELECT is the state at the point in time at the beginning of the transaction, so the results read by the same SELECT operation will be consistent. However, there are phantom readings (explained later).
  • Serialization (SERIALIZABLE). Read operations implicitly acquire shared locks, which ensure mutual exclusion between different transactions.

The four levels are progressively stronger, and each level solves a problem.

  • Dirty read, easiest to understand. Another transaction modifies the data, but has not yet committed, and the SELECT in this transaction will read the uncommitted data.
  • Do not repeat reading. After solving the dirty read, it will be encountered that another transaction submits new data during the execution of the same transaction, so the results of the data read twice by this transaction will be inconsistent.
  • Phantom reading. It solves the problem of non-repeat reading, and ensures that in the same transaction, the result of the query is the state (consistency) at the beginning of the transaction. However, if another transaction submits new data at the same time, when this transaction is updated again, it will be "surprised" to discover these new data, it seems that the data read before is an illusion like "ghosting".

Borrowed and adapted a hilarious metaphor:

  • Dirty read. If you go to the cafeteria for lunch at noon and see that a seat is occupied by classmate Q, you will think that the seat is occupied and turn around to find another seat. Unexpectedly, this classmate Xiao Q got up and left. Fact: The student, Xiao Q, just sat for a while and did not "submit".
  • Do not repeat reading. If you go to the cafeteria for lunch at noon and see that a seat is empty, you will go to the dining hall, but when you come back, you will find that the seat has been occupied by classmate Q.
  • Phantom reading. If you go to the cafeteria for lunch at noon, and see that a seat is empty, you will go to the dining hall in a frenzy, and when you come back, you will find that these seats are still empty (repeat), and you will be delighted. When I was about to sit down, I was surprised to see a dinosaur girl, which seriously affected my appetite. As if the empty seats seen before were "phantoms".

------

Some articles have written that InnoDB's repeatable reads avoid "phantom reads", which is not accurate.

Do an experiment: (all the following experiments should pay attention to the storage engine and isolation level)

mysql> show create table t_bitfly\G;
CREATE TABLE `t_bitfly` (
`id` bigint(20) NOT NULL default '0',
`value` varchar(32) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk

mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

Test one:

t Session A                   Session B
|
| START TRANSACTION;          START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| empty set
|                             INSERT INTO t_bitfly
|                             VALUES (1, 'a');
|
| SELECT * FROM t_bitfly;
| empty set
|                             COMMIT;
|
| SELECT * FROM t_bitfly;
| empty set
|
| INSERT INTO t_bitfly VALUES (1, 'a');
| ERROR 1062 (23000):
| Duplicate entry '1' for key 1
v (shit, 刚刚明明告诉我没有这条记录的)

如此就出现了幻读,以为表里没有数据,其实数据已经存在了,傻乎乎的提交后,才发现数据冲突了。

试验二:

t Session A                  Session B
|
| START TRANSACTION;         START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                            INSERT INTO t_bitfly
|                            VALUES (2, 'b');
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                            COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|
| UPDATE t_bitfly SET value='z';
| Rows matched: 2  Changed: 2  Warnings: 0
| (怎么多出来一行)
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | z     |
| |    2 | z     |
| +------+-------+
|
v

本事务中第一次读取出一行,做了一次更新后,另一个事务里提交的数据就出现了。也可以看做是一种幻读。

------

那么,InnoDB指出的可以避免幻读是怎么回事呢?

http://dev.mysql.com/doc/refman/5.0/en/innodb-record-level-locks.html

By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 13.6.8.5, “Avoiding the Phantom Problem Using Next-Key Locking”).

准备的理解是,当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-key locks可以避免幻读。

关键点在于,是InnoDB默认对一个普通的查询也会加next-key locks,还是说需要应用自己来加锁呢?如果单看这一句,可能会以为InnoDB对普通的查询也加了锁,如果是,那和序列化(SERIALIZABLE)的区别又在哪里呢?

MySQL manual里还有一段:

13.2.8.5. Avoiding the Phantom Problem Using Next-Key Locking (http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html)

To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking.

You can use next-key locking to implement a uniqueness check in your application: If you read your data in share mode and do not see a duplicate for a row you are going to insert, then you can safely insert your row and know that the next-key lock set on the successor of your row during the read prevents anyone meanwhile inserting a duplicate for your row. Thus, the next-key locking enables you to “lock” the nonexistence of something in your table.

我的理解是说,InnoDB提供了next-key locks,但需要应用程序自己去加锁。manual里提供一个例子:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

这样,InnoDB会给id大于100的行(假如child表里有一行id为102),以及100-102,102+的gap都加上锁。

可以使用show innodb status来查看是否给表加上了锁。

再看一个实验,要注意,表t_bitfly里的id为主键字段。实验三:

t Session A                 Session B
|
| START TRANSACTION;        START TRANSACTION;
|
| SELECT * FROM t_bitfly
| WHERE id<=1
| FOR UPDATE;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                           INSERT INTO t_bitfly
|                           VALUES (2, 'b');
|                           Query OK, 1 row affected
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                           INSERT INTO t_bitfly
|                           VALUES (0, '0');
|                           (waiting for lock ...
|                           then timeout)
|                           ERROR 1205 (HY000):
|   

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325569535&siteId=291194637