INNODB间隙锁

原文链接:http://www.mysqlperformanceblog.com/2012/03/27/innodbs-gap-locks/

One of the most important features of InnoDB is the row level locking. This feature provides better concurrency under heavy write load but needs additional precautions to avoid phantom reads and to get a consistent Statement based replication. To accomplish that, row level locking databases also acquire gap locks.

What is a Phantom Read

A Phantom Read happens when in a running transaction, two identical statements get different values, because some other transaction has modified the table’s rows. For example:

INNODB的一个最重要的特征是行级锁。这个特征能在高写入负载下提供良好的并发,但是需要额外的处理措施去解决幻读和获得基于语句模式下复制的一致性。为了达到这个目标,支持行级锁的数据库也会获取间隙锁。

什么是幻读

幻读发生在一个活动的事务中,两个相同的语句获取到不同的值,因为其他一些的事务改变了表的记录,比如:

 transaction1> START TRANSACTION;

 transaction1> SELECT * FROM t WHERE i > 20 FOR UPDATE;

 +------+

 | i |

 +------+

 | 21 |

 | 25 |

 | 30 |

 +------+
 transaction2> START TRANSACTION;

 transaction2> INSERT INTO t VALUES(26);

 transaction2> COMMIT;
transaction1> select * from t where i > 20 FOR UPDATE;

 +------+

 | i |

 +------+

 | 21 |

 | 25 |

 | 26 |

 | 30 |

 +------+

Phantom reads do not occur if you’re simply doing a SELECT. They only occur if you do UPDATE or DELETE or SELECT FOR UPDATE. InnoDB provides REPEATABLE READ for read-only SELECT, but it behaves as if you use READ COMMITTED for all write queries, in spite of your chosen transaction isolation level (considering only the two most common isolation levels, REPEATABLE READ and READ COMMITTED).

如果只是执行简单的查询操作,是不会发生幻读的。只有当你执行UPDATE 或 DELETE 或 SELECT FOR UPDATE幻读才会发生。INNODB为只读操作提供可重复读事务隔离级别,但是它取决于你是否对全部对写操作使用了READ COMMITTED隔离级别,而不管你是否选择了事务隔离级别(只考虑最广泛使用的隔离级别,可重复读,提交读)。

What is a gap lock? 什么是间隙锁?

A gap lock is a lock on the gap between index records. Thanks to this gap lock, when you run the same query twice, you get the same result, regardless other session modifications on that table. This makes reads consistent and therefore makes the replication between servers consistent. If you execute SELECT * FROM id > 1000 FOR UPDATE twice, you expect to get the same value twice. To accomplish that, InnoDB locks all index records found by the WHERE clause with an exclusive lock and the gaps between them with a shared gap lock.

This lock doesn’t only affect to SELECT … FOR UPDATE. This is an example with a DELETE statement:

间隙锁就是锁住索引记录之间的间隙的锁(真直白)。多亏有这个间隙锁,当你2次运行相同的查询时,你可以得到相同的结果集,而不用管是否有其他的连接修改了表数据。这实现了一致性读,因此也实现了服务器之间复制的一致性。如果你执行SELECT * FROM id > 1000 FOR UPDATE 2次,你期望得到相同的结果集。为了实现这个目的,INNODB以独占锁锁定全部通过WHERE条件找到的索引项,并用共享锁锁定他们之间的间隙。

这种锁不只是对SELECT … FOR UPDATE起作用,这个例子用DELELTE语句:

transaction1 > SELECT * FROM t;

 +------+

 | age |

 +------+

 | 21 |

 | 25 |

 | 30 |

 +------+

Start a transaction and delete the record 25: 开始一个事务,删除记录25

transaction1 > START TRANSACTION;

transaction1 > DELETE FROM t WHERE age=25;

At this point we suppose that only the record 25 is locked. Then, we try to insert another value on the second session:

此时我们假设只锁定了记录25,然后尝试在另外一个连接插入别的值

 transaction2 > START TRANSACTION;

 transaction2 > INSERT INTO t VALUES(26);

 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 transaction2 > INSERT INTO t VALUES(29);

 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 transaction2 > INSERT INTO t VALUES(23);

 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 transaction2 > INSERT INTO t VALUES(31);

 Query OK, 1 row affected (0.00 sec)

After running the delete statement on the first session, not only the affected index record has been locked but also the gap before and after that record with a shared gap lock preventing the insertion of data to other sessions.

在第一个连接中执行了删除语句之后,不仅仅只锁住了被影响到的索引项,为了防止其他连接插入数据,也用共享锁锁住了这条记录的前后间隙记录。

How to troubleshoot gap locks? 怎么检查间隙锁

Is possible to detect those gap locks using SHOW ENGINE INNODB STATUS: 可以通过SHOW ENGINE INNODB STATUS 检测间隙锁

---TRANSACTION 72C, ACTIVE 755 sec

 4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1

 MySQL thread id 3, OS thread handle 0x7f84a78ba700, query id 163 localhost msandbox

 TABLE LOCK table `test`.`t` trx id 72C lock mode IX

 RECORD LOCKS space id 19 page no 4 n bits 80 index `age` of table `test`.`t` trx id 72C lock_mode X

 RECORD LOCKS space id 19 page no 3 n bits 80 index `GEN_CLUST_INDEX` of table `test`.`t` trx id 72C lock_mode X locks rec but not gap

 RECORD LOCKS space id 19 page no 4 n bits 80 index `age` of table `test`.`t` trx id 72C lock_mode X locks gap before rec

If you have lot of gaps locks in your transactions affecting the concurrency and the performance you can disable them in two different ways:

如果你的事务里面存在很多间隙锁而影响了并发和性能,你可以用下面2种不同的办法禁用它。

1- Change the ISOLATION level to READ COMMITTED. In this isolation level, it is normal and expected that query results can change during a transaction, so there is no need to create locks to prevent that from happening. 2- innodb_locks_unsafe_for_binlog = 1. Disables the gap locks except for foreign-key constraint checking or duplicate-key checking.

1-把事务隔离级别修改为READ COMMITTED。在这个隔离级别下,在一个事务中查询结果被改变了是正常的和预期的,所以不需要创建锁去防止这样的事情(幻读)发生。

2-innodb_locks_unsafe_for_binlog = 1. 禁用间隙锁,除了外键约束检查和重复主键检查。

The most important difference between these two options is that the second one is a global variable that affects all sessions and needs a server restart to change its value. Both options cause phantom reads (non repeatable reads) so in order to prevent problems with the replication you should change the binary log format to “row”.

这2个参数最重要的差别是,第二个参数是全局变量,它会影响到所有的连接,并需要重启服务才能修改参数值。这2个参数都会导致幻读(非可重复读),所有为了防止复制的问题,你应该把二进制日志记录格式改成行模式。

Depending on the statement, the behavior of these locks can be different. In the following link there is a good source of information:

根据语句的不同,这些锁的行为会有所不同,下面的连接是非常好的信息来源:

http://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.html.

 

Conclusion  结论

MySQL uses REPEATABLE READ as the default isolation level so it needs to lock the index records and the gaps to avoid phantom reads and to get a consistent Statement based replication. If your application can deal with phantom reads and your binary log is in row format, changing the ISOLATION to READ COMMITTED will help you to avoid all those extra locks. As a final advice, keep your transactions short

MYSQL用REPEATABLE READ作为默认的事务隔离级别,所以它需要锁定索引记录和间隙记录,去解决幻读和基于语句的复制的一致性。如果你的应用能够处理幻读并且二进制登记为行格式,把隔离级别改成READ COMMITTED将帮助你避免所有的这些额外锁。最后一个建议,坚持使用短事务。

 

《完》 2012-09-28 13:47:57

你可能感兴趣的:(InnoDB)