Skip to content

锁机制

Mysql是支持多线程的,在同一时刻可以处理多个用户的请求,如果多个用户都在修改同一个数据,就会造成数据的不稳定性,锁机制的出现是为了保证数据的稳定性,如我们在同一时刻进行买书的操作,我们要锁住记录,在扣除余额的时候是一个一个进行的,而不要受并发多线程的影响来同时进行余额的扣除

事务处理中的锁机制

行级锁

使用锁机制,我们可以将整张表进行锁定,即表级锁,也可以使用行级锁定的方式,将表中的某条数据记录进行锁定,行级锁在应付高并发的时候响应比较快,其存储性能是比较高的,在数据引擎中InnoDB是支持行级锁的

如果两个事务都执行一个事务,都对数据表中的同一条数据的同一个字段内容进行修改,先执行的事务可以正常修改,但是后执行的事务在执行sql语句的时候就会阻塞,一直到先执行的事务提交后,阻塞才会重新恢复,并执行下去。如果在锁机制阻塞的时候,我们将其终止,更新其他数据的内容,是可以更新成功的。

上述过程是一个行级锁的体现,没有锁定整张表,只是锁定了表中的某行数据

索引

对于没有设置索引的字段,即使修改的不是同一条数据,但是根据这个字段取查找的:

sql
UPDATE stu SET sname = '小金' WHERE sname = 'xiaojin';

其中sname字段没有索引数据,不同事务通过该字段进行索引从而修改内容,会导致表的记录被大面积的锁定

一般情况下,主键是默认添加了索引的,其他字段是没有添加索引的,如果使用的列不是索引列,就会将整个表的索引记录进行锁定,这样就丢失了InnoDB行级锁的魅力

对于查询量多的字段,我们最好可以将其设置成索引,这样就不会造成大面积的锁定

查询范围对锁的影响

在查询范围内的所有记录都会被锁定,但是在范围外的记录是不会被锁定的

对于事务A,对主键id大于1和小于5之间的num字段内容进行修改:

sql
SET autocommit = 0;   # 开启全局事务
UPDATE goods SET num=500 WHERE id>1 AND id<5;

对于事务B,在查询范围id大于1且小于5的内容进行修改,会被锁定;对范围之外的内容进行修改不会被锁定:

sql
SET autocommit = 0;
UPDATE goods SET num=200 WHERE id=3;    # 发生阻塞
UPDATE goods SET num=200 WHERE id=5;    # 不发生阻塞

只有当事务A提交后,那事务B就可以该怎么操作怎么操作了,不会受到影响了

我们是希望锁的查询范围是越小越好,这样使多线程的Mysql应用在处理高并发的时候,性能可以更好


悲观锁

Mysql中总是感觉这个数据在更新的时候,别人也在更新,我们可以使用悲观锁,在查询的时候就进行锁定

当有一个事务在查询的时候,其他事务的查询操作也被锁定了,这个情况就是悲观锁(当前一个事务在查询的时候,后面的事务就不要查询了,将查询操作阻塞了)

事务A:

sql
SET autocommit = 0;
SELECT * FROM goods WHERE id=1 FOR UPDATE;   # 对id为1的商品进行查询,执行悲观锁

事务B:

sql
SET autocommit = 0;
SELECT * FROM goods WHERE id=1 FOR UPDATE; # 也对id为1进行查询,由于事务A设置了悲观锁,当前会阻塞

当事务A执行提交或回滚操作后,即结束事务,事务A剩余执行的sql语句:

sql
UPDATE goods SET num=0 WHERE id=1;   # 将id为1的商品全部买完
COMMIT;

事务B的阻塞才会终止,返回查询的结果,返回的结果:id为1的商品数量为0


乐观锁

Mysql中有悲观锁,对应的也有乐观锁,更新数据的时候很乐观,不认为别人也会更新数据,如果出现了问题,等出现了问题再说,一般通过其他字段进行限制,常用的是版本号字段

事务A:

sql
SET autocommit = 0;
SELECT * FROM goods WHERE id=1;   # 对id为1的商品进行查询
# 买100件商品,同时让版本号加一
UPDATE goods SET num=num-100,version=version+1 WHERE version=0 AND id=1;  
COMMIT;

事务B:

sql
SET autocommit = 0;
# 版本号已经发生改变,是索引不到这个商品的,因此也买不到这个商品
UPDATE goods SET num=num-100,version=version+1 WHERE version=0 AND id=1;

通过这样的乐观锁,保持了数据的稳定性,从而不会出现数据的错乱


表锁的技巧

对于一些不能支持事务的引擎,我们可以使用锁表的机制来保证数据的稳定

读锁

对表进行锁定,使该表只能进行读取,不能进行其他操作,包括当前用户:

sql
LOCK TABLE goods READ;
SELECT * FROM goods;   # 可以读取,当前会话和其他会话都可以读取
INSERT INTO goods (name,num)VALUES('手机',200);   # 不能执行,当前会话报错,其他会话发生阻塞
UNLOCK TABLES;  # 执行解锁操作,其他会话阻塞结束,执行了之前的操作

写锁

写锁可以使当前会话可以操作这张表,其他会话什么都不能执行,阻塞,只有当前会话解锁后,其他会话才能使用

sql
LOCK TABLE goods WRITE;
SELECT * FROM goods;
UNLOCK TABLES;

锁表不但但只能锁一个表,可以一起锁定很多表:LOCK TABLE goods WRITE, stu WRITE;

这样写锁的方式会造成对整个表的锁定,对高并发的吞吐处理能力是非常有限的,一般不太建议使用写锁的方法

Released under the MIT License.