Mysql锁之共享锁(读锁)和排他锁(写锁)详解

 更新时间:2023年07月27日 09:55:40   作者:烟雨楼台笑江湖  
这篇文章主要介绍了Mysql锁之共享锁(读锁)和排他锁(写锁),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

InnoDB和MyISAM

Mysql在5.5之前默认使用MyISAM存储引擎,之后使用InnoDB。

查看当前存储引擎:

show variables like ‘%storage_engine%';

MyISAM操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然它也不会存在死锁问题。

InnoDB与M有ISAM的最大不同有两点

  • InnoDB支持事务
  • InnoDB采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。

在Mysql中,行级锁并不是直接锁记录,而是锁索引。

索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,Mysql就会锁定这条主键索引;如果一条语句操作了非主键索引,Mysql会先锁定该非主键索引,再锁定相关的主键索引。

InnoDB行锁是通过给索引加锁实现的,如果没有索引,InnoDB会通过因此的聚簇索引来对记录加锁。

也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到记录就得扫描全表,要扫描全表,就得锁定表。

共享锁与排他锁

首先说明:InnoDB引擎默认对update,delete,insert加排它锁,select语句默认不加锁

共享锁

共享锁shared locks(S锁)也称读锁:用于不更改或不更新数据的操作(只读操作),可以查看但无法修改和删除的一种数据锁,如select语句。

如果事务T对数据A加上共享锁后,则其他事务只能对数据A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享。

加锁方式

select ... lock in share mode

注意:

  • 对于使用共享锁的事务,其他事务只能读,不可写
  • 如果执行了更新操作则会一直等待,直到当前事务commit或者rollback
  • 如果当前事务也执行了其他事务处于等待的那条sql语句,当前事务将会执行成功,而其他事务会报死锁
  • 允许其他锁共存

排它锁

排它锁Exclusive Locks(X锁)也称写锁、独占锁:用于数据修改操作,例如insert、update或delete。确保不会同时对同一资源进行多重更新。

如果事务T对数据A加上排它锁后,则其他事务不能在对A加任何类型的锁。获准排他锁的事务既能读数据,又能修改数据。我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)

加锁方式

select ... for update

**for update:**InnoDB默认是行级别的锁,当有明确指定的主键是,使用的是行锁;否则使用表锁。使用情况详细如下:

明确指定主键,并且存在此记录,行级锁。例如:

-- id是主键
select name,age from table_user where id = '1' for update;

明确指定主键,若查无记录,无锁。例如:

-- id是主键,单不存在id = 1的数据
select name,age from table_user where id = '1' for update;

无主键,表级锁。例如:

-- age是普通字段
select name,age from table_user where age = 12 for update;

主键不明确,表级锁。例如:

-- id是主键,age不是,但数据库
select name,age from table_user where age = 12 and id = '1' for update;

注意:

  • 对于排它锁的事务,其它事务可读,但不可进行更新操作
  • for update仅使用与InnoDB,并且必须开启事务,在begin和commit之间才生效
  • 当一个事务进行for update的时候,另一个事务也有for update时会一直等待,直到之前的事务commit或rollback或断开连接释放锁才能拿到锁进行后面的操作(排它锁不能共存)
  • InnoDB引擎默认对update,delete,insert加排它锁,select语句默认不加锁
  • 加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制

乐观锁与悲观锁

首先说明:乐观锁和悲观锁都是针对读(select)来说的。

乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。

乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

悲观锁

悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟Java中的synchronized很相似,所以悲观锁需要耗费较多的时间。

另外与乐观锁相对应,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

由悲观锁涉及到的另外两个锁概念,就是共享锁与排他锁。共享锁和排他锁是悲观锁的不同实现,它俩都属于悲观锁的范畴。

案例

某商品,用户购买后库存应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为“n”,之后进行了一些操作,最后将均执行update table set 库存书 = n - 1,那么,很显然这是错误的。

解决

1.使用悲观锁(也就是排他锁)

  • 程序A在查询库存数时使用排他锁(select * from table where id = 10 for update)
  • 然后进行后续操作,包括更新库存数,最后提交事务
  • 程序B在查询库存数时,如果A还未释放排他锁,程序B将等待。。。
  • 程序C同B。。。

2.使用乐观锁(靠表设计和代码来实现)

一般是在该商品表添加version版本字段或者timestamp时间戳字段

程序A查询后,执行更新变成了:

update table set num = num - 1 and version = 23

这样,保证了修改的数据是和它查询出来的数据是一致的(其他执行程序肯定未进行修改)。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。

乐观锁和悲观锁的区别

悲观锁实际使用了排他锁来实现**(select … for update)**。InnoDB加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。

因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许**“加锁的查询操作”**,影响吞吐。因此,如果在查询居多的情况下,推荐使用乐观锁。

加锁的查询操作:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for update或lock in share mode的加锁方式查询,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制。

乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。

结尾:读用乐观锁,写用悲观锁。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

最新评论