MySQL加锁

最近碰到了MySQL中的for update语法,这是用来加锁的,将学习的过程记录下来



悲观锁

悲观锁(Pessimistic Lock)认为每次读取数据都是不安全的,所以每次读取数据的时候都会加锁。
比如mysql里的for update就属于这类,可以锁一行,也可锁住整个表,加锁之后别人就无法同时进行操作了。
悲观锁一般用于写操作比较多的地方。


乐观锁

乐观锁(Optimistic Lock)认为每次读取数据的时候是安全的,只有在更新操作的时候才会判断是否有别人修改过数据。
这种锁一般通过其它方式来控制,比如通过版本号机制等。
乐观锁一般用于读操作比较多的地方,这样可以提高吞吐量。


for update用法

先来一个测试用的表:

CREATE TABLE `user` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `user_name` varchar(20) NOT NULL COMMENT '用户名',
  `remark` varchar(20) NOT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';

表中数据:

+----+-----------+---------+
| id | user_name | remark  |
+----+-----------+---------+
|  1 | user1     | remark1 |
|  2 | user2     | remark2 |
|  3 | user3     | remark3 |
+----+-----------+---------+

这样来使用for update

BEGIN;
SELECT * FORM user WHERE id=1 for update;
COMMIT;

注意:

  1. for update必须在BEGIN/COMMIT块中才会起作用
  2. 明确使用索引的,会使用行锁(Row Lock),不使用索引或索引不明确(如id>2),会使用表锁(Table Lock)

我们来测试一下,通过两个终端分别进行操作,如图:

测试图片1

测试图片2

我们在第一个终端中开启事务使用了行锁,但未commit,第二个终端中再查询该行则会被阻塞,处于等待状态。 直到第一个终端commit之后,第二个终端才会读取到被锁住的那一行的数据。 若等待时间过长(终端1迟迟未commit),则终端2会产生超时错误。


测试记录

主要是测试下,什么情况下是 Row Lock,什么情况下是 Table Lock。

以下情况是 Row Lock

-- 使用了主键索引
select * from user where id=1 for update;
-- 使用了其它索引
select * from user where user_name='user1' for update;
-- 使用 like 但是可以唯一确定一行
select * from user where user_name like 'user1%' for update;

以下情况是 Table Lock

-- 主键不明确
select * from user where id != 1 for update;
-- 没有用上任何索引
select * from user where remark='remark1' for update;
select * from user where user_name like '%user1' for update;  -- 虽然能确定一行,但是没用上索引
-- 使用了 like 不能唯一确定一行
select * from user where user_name like 'user%' for update;   -- 这条用上了索引
select * from user where user_name like '%user%' for update;  -- 这条没有用上索引
-- 未查到数据,但是未使用任何索引
select * from user where remark='test' for update;

以下情况 无 Lock

-- 未查到数据,但是使用了索引
select * from user where user_name='user9' for update;