数据库的锁

Posted by JimWang on 2021-02-20

数据库的锁

锁分为乐观锁和悲观锁。
其中悲观锁按照类型可以划分为:

  • 共享锁(读锁)
  • 排他锁(写锁)

也可以按照锁的范围分为:

  • 行锁
  • 表锁

乐观锁

假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

乐观锁是一种不会阻塞其他线程并发的控制,它不会使用数据库的锁进行实现,它的设计里面由于不阻塞其他线程,所以并不会引起线程频繁挂起和恢复,这样便能够提高并发能力,所以也有人把它称为非阻塞锁。
一般的实现乐观锁的方式就是记录数据版本。(数据版本:为数据增加的一个版本标识。)

当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。

使用版本号实现乐观锁:

使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。

1
2
3
4
5
6
7
1.查询出商品信息
select (status,status,version) from t_goods where id = #{id}
2.根据商品信息生成订单
3.修改商品status2
update t_goods
set status = 2,version = version + 1
where id = #{id} and version = #{version};

悲观锁

假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

悲观锁是一种利用数据库内部机制提供的锁的方式,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新了,这就是悲观锁的实现方式。

行锁

MySQL 5.6 以后Engine db 默认是行锁,当然Engine也支持表锁,mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型

表锁

MySQL 的MyISAM 的默认锁为表锁,当数据库表设置为MyISAM后,操作该数据库都会加表锁,其中 查询数据会加读锁(共享锁),修改、增加、删除数据会加写锁(排他锁)

1
2
3
4
5
6
7
8
9
10
11
12
# 由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例) ,否则MySQL将会执行Table Lock (将整个资料表单给锁住):
# (明确指定主键,并且有此笔资料,row lock)
SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;
# (明确指定主键,若查无此笔资料,无lock)
SELECT * FROM products WHERE id='-1' FOR UPDATE;
# (无主键,table lock)
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
# (主键不明确,table lock)
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
# (主键不明确,table lock)
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;

排他锁(写锁)

sql语句:select ...for update

如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行共享锁和排他锁只有获取到排它锁的事务支持对数据行的修改。

共享锁(读锁)

sql语句:select ... lock in share mode

共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

举例

举例,使用排他锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//0.开始事务
begin;/begin work;/start transaction; (三者选一)
//1.查询出商品信息
select status from t_goods where id = 1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;

上面的查询语句中,我们使用了 selectfor update 的方式,这样就通过开启排他锁的方式实现了悲观锁。
此时在t_goods表中,id1的那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。
这样我们可以保证当前的数据不会被其它事务修改。

需要注意,innodb默认的锁级别是行锁


部分转载自:https://www.yuque.com/fanzhengxu/tba6b8/dx0hvw#eDkqD