先上定义:
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
在此先引入MySQL中锁的分类:
其中,独占锁是写锁,可理解为传统意义上的读写锁,读读不互斥,读写互斥,写写互斥。
排他锁用法:
SELECT ... FROM UPDATE;
排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。
for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
验证排他锁,设计一张表:
/* 开启事务1 */
BEGIN;
/* 查询name为张三的数据并加上排他锁 */
SELECT * FROM test WHERE name = '张三' FOR UPDATE;
/* 延迟10秒执行 */
SELECT SLEEP(10);
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 5000 WHERE name = '张三';
/* 延迟15秒执行 */
SELECT SLEEP(15);
/* 提交事务1 */
COMMIT;
开启事务1的执行过程如下:
同时开启事务二:
/* 开启事务2 */
BEGIN;
/* 普通查询name = '张三'的数据 */
SELECT * FROM test WHERE name = '张三';
/* 提交事务2 */
COMMIT;
事务二很快执行完成了,执行结果如下:
可以看出在事务一开启了排他锁以后,其他事务(事务二)仍然可以进行读操作,并产生了不可重复读的结果(在事务一没提交之前,事务一修改张三的余额为5000,事务二读取的张三的余额却为7000,待事务一执行完毕后,事务二读取的张三的余额变为5000)。
如果事务一不变的情况下
/* 开启事务1 */
BEGIN;
/* 查询name为张三的数据并加上排他锁 */
SELECT * FROM test WHERE name = '张三' FOR UPDATE;
/* 延迟10秒执行 */
SELECT SLEEP(10);
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 5000 WHERE name = '张三';
/* 延迟15秒执行 */
SELECT SLEEP(15);
/* 提交事务1 */
COMMIT;
事务二对张三的余额进行修改操作
/* 开启事务2 */
BEGIN;
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 2000 WHERE name = '张三';
/* 提交事务2 */
COMMIT;
可以看到在事务一开启排他锁的期间,事务二无法进行修改操作,等待20多秒事务一提交后才执行了事务二。事务二在事务一后执行,所以张三的余额被修改为2000。