上一章我们讲到,MyISAM和MEMORY存储引擎采用的是表级锁;BDB存储引擎采用的是页面锁,但也支持表级锁;InnoDB存储引擎即支持行级锁,也支持表级锁。
可以通过检查InnoDB_row_lock状态变量来分析系统上行锁的争夺情况:
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.01 sec)
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高
可以通过查询information_schema数据库的表了解锁等待情况:
mysql> use information_schema;
Database changed
mysql> select * from innodb_locks;
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
| 1148205:30:3:2 | 1148205 | X | RECORD | `miaosha`.`goods` | PRIMARY | 30 | 3 | 2 | 100000 |
| 1148203:30:3:2 | 1148203 | X | RECORD | `miaosha`.`goods` | PRIMARY | 30 | 3 | 2 | 100000 |
+----------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)
mysql> select * from innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 1148212 | 1148212:30:3:3 | 1148213 | 1148213:30:3:3 |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)
InnoDB实现了两种类型行锁:
对于UPDATE、DELETE和INSERT语句InnoDB会自动给涉及数据集加排它锁(X);对于普通SELECT语句,InnoDB不会加任何锁;
事务可以通过一下语句显示给记录集加共享锁或排他锁:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
SELECT * FROM table_name WHERE ... LOCK for update
共享锁
用SELECT …IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则有可能造成死锁,对于锁定记录后进行更新操作,应该使用SELECT…FOR UPDATE方式获得排它锁。
前面提到update操作,会自动的加排它锁(X),在session2获得共享锁之后,session1的update操作想获得排它锁而进入阻塞状态,这时候session2如果继续对进入阻塞状态的记录去占用锁就会导致死锁,注意在同session中获取共享锁和排它锁不会进入阻塞状态。
排它锁
MySQL的行锁是针对索引加的锁,不是针对记录加的锁,不管是单索引还是多索引,在检查锁是否冲突的时候,要看它的索引键是否出现冲突。
(1)在不通过索引条件查询时,InnoDB会锁定表中所有记录。
(2)索引和非索引的情况
如下图所示store_id为索引,first_name为非索引,session1和session2为两条索引store_id=1的不同记录
(3)多索引的情况
如图customer_id和rental_id分别对应两个索引,MySQL行锁会给两个索引都加锁,当两个索引对应不同的记录的时候,不会有锁冲突的情况,MySQL行锁会给两个索引都加上锁。
在MySQL中,是否使用索引是通过执行计划Explain来决定的,如果执行计划没有走索引,InnoDB就会对所有记录加锁,反之则是行锁。
另外,为了允许行锁和表锁共存,实现多粒度机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这种意向锁都是表锁。
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放,意向锁是InnoDB自动加的,不需要用户干预。
意向锁的主要目的是表明有人正在锁定表中的行,或者打算锁定表中的行,试想一下如果没有意向锁,假设现在要获取一个表锁,是不是要一行行检查是否有行锁才可以?
InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。InnoDB行锁分为3种情形。
记录锁是对索引记录的锁定。
间隙锁是对索引记录之间间隙的锁定,或者是对第一条记录或最后一个索引记录之前的间隙的锁定。
间隙锁是性能和并发性之间权衡的一部分,并且在某些事务隔离级别而非其他级别中使用。
对于使用唯一索引来锁定的语句,不需要间隙锁定,对于非唯一和没有索引的语句,需要间隙锁定。
Next-key lock:前两种的组合,对记录及其前面的间隙加锁。
在范围查询条件中,使用共享锁或者排它锁,在键值查询条件不存在的记录就叫做"间隙"(GAP),如果只对不存在“间隙”加锁,就是Grap lock锁,对记录和"间隙"的都加锁,就是Next-key 锁。
举例来说,假设有一张user表
CREATE TABLE `user` (
`id` int(1) NOT NULL AUTO_INCREMENT,
`name` varchar(8) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '小罗');
INSERT INTO `user` VALUES ('7', '小明');
INSERT INTO `user` VALUES ('11', '小红');
INSERT INTO `user` VALUES ('18', '老紫');
INSERT INTO `user` VALUES ('20', '紫涵');
也就是id得区间范围是
[1,7)
[7,11)
[11,18)
[18,20]
我们来根据具体得例子来具体分析:
对记录数据加锁:
select * from `user` where id =11 FOR UPDATE;
select * from `user` where id BETWEEN 7 and 11 FOR UPDATE;
记录7,11会被加记录锁,(7,11)(12,18)会被加间隙锁。
SELECT * FROM `user` WHERE `id` >= 7 FOR UPDATE;
记录7、11、18会被加记录所,(7,11)(11,18)(18,+∞)会被加间隙锁。
对间隙加锁:
select * from `user` where id =14 FOR UPDATE;
select * from `user` where id BETWEEN 14 and 16 FOR UPDATE;
SELECT * FROM `user` WHERE `id` >= 15 FOR UPDATE;
上述示例是在mysql默认隔离级别REPEATABLE-READ下,实际上,在不同的隔离级别下,InnoDB处理SQL时采用的一致性读策略和需要的锁是不同的。
锁是多久就会自动释放?
答:innodb_lock_wait_timeout默认是50秒