一个InnoDB 加锁的案例

本文通过一个InnoDB加锁的案例,探讨了InnoDB如何在行级锁定时处理索引记录,解释了为何对主键查询可能会锁住较大范围的原因,并涉及到gap lock、next-key lock和record lock的概念,强调了加索引对于防止幻读和优化查询速度的重要性。
摘要由CSDN通过智能技术生成

        最近一直在优化各个产品的SQL语句,同时还帮一个同事解决deadlock问题,收获就是对InnoDB加锁的理解更加深入了。先来看看今天的这个案例:

mysql> select version();
+----------------------+
| version()            |
+----------------------+
| 5.5.13.4-log         |
+----------------------+

mysql> show variables like '%iso%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

CREATE TABLE `t1` (
  `a` int(11) NOT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB;

mysql> select * from t1;
+----+------+------+
| a  | b    | c    |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    1 |    1 |
|  3 |    1 |    1 |
| 10 |    1 |    1 |
| 11 |    1 |    1 |
| 12 |    1 |    1 |
| 14 |    1 |    1 |
| 15 |    1 |    1 |
+----+------+------+

         测试环境就是如上,开始下面的测试:

session1:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1 where a in (2, 10, 11, 12,14) for update;
+----+------+------+
| a  | b    | c    |
+----+------+------+
|  2 |    1 |    1 |
| 10 |    1 |    1 |
| 11 |    1 |    1 |
| 12 |    1 |    1 |
| 14 |    1 |    1 |
+----+------+------+
5 rows in set (0.00 sec)

session2:
mysql> insert into t1 values(7, 1, 1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t1 where a=15 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t1 values(18,1,1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t1 values(18000,1,1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

        从上面可以看出InnoDB仿佛是将[2, 无穷大)这个区间给锁了,那么原因是什么呢? 你可以先思考下

        再来看第二组测试

session1:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1 where a in (2, 14) for update;
+----+------+------+
| a  | b    | c    |
+----+------+------+
|  2 |    1 |    1 |
| 14 |    1 |    1 |
+----+------+------+
2 rows in set (0.00 sec)

session2:
mysql> insert into t1 values(7, 1, 1);
Query OK, 1 row affected (0.00 sec)

       从上面可以看出,这次2与14之间的区间并没有被锁住(14后面的肯定也没锁,只是没贴出结果而已),那这又是为什么呢?

        要理解上面的现象首先得明白InnoDB是怎么加锁的。在InnoDB加锁时它是这样做的:InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters.也就是说它是在扫描表或者索引时遇到的行都会被加锁,一定请注意encounters这个单词,是扫描过程中遇到的行都会被锁住。那么再来看上面的现象,为什么对主键的查询还会锁一个那么大的区间呢?首先第一感觉肯定是只加in()中列出的几行,但又想InnoDB是对扫描过程中遇到的行都加锁,所以曾误以为where a in(val1, ..., valn)这种形式的会是锁val1到valn中最小、最大值组成的区间(因为觉得扫描就是从2到14),但是上面的结果不是显示18000也无法插入吗?这又是为什么呢?后来经同事指点才猛然大悟:

mysql> explain select * from t1 where a in (2, 10, 11, 12,14) for update;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | test  | ALL  | PRIMARY       | NULL | NULL    | NULL |    8 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
        看了这个之后相信大家都明白原因了吧,因为这个查询走的是全表扫描,所以所有的行都加了记录,包括那些现在不存在记录的区间。那么对于第二个例子呢?

mysql> explain select * from t1 where a in (2, 14) for update;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | test  | range | PRIMARY       | PRIMARY | 4       | NULL |    2 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
        从上面可以看出,这次没走全表扫描,而是主键索引(range,将扫描区间分为[2, 2], [14, 14]两者区间),因为主键索引可以定位到具体的行,自然加锁的行就只有2和14了。

        从这个小例子也可以联想到InnoDB的三种类型的锁gap lock/next-key lock/record lock。为什么gap lock/next-key lock是锁一个区间(当然[a, a+1)也是一个区间,这个区间只是特殊点只有一条记录而已)。它的目的是防止幻读。它为什么能实现防止幻读?因为扫描过程中的区间全部加锁,那自然下次再读的时候别人是无法插入数据进来的。

        从这个小例子我们也可以明白一点为什么加索引如此重要。假如没有合适的索引,不仅查询的速度会很慢,而当你对表进行update/delete的时候你会发现很多意想不到的记录被锁住,导致锁等待超时或者死锁错误。


参考文章

  1. refman-5.5-en.pdf 13.3.9/The InnoDB Transaction Model and Locking

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值