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 …查询数据,因为普通查询没有任何锁机制。
乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。
结尾:读用乐观锁,写用悲观锁。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
win10下mysql 8.0.16 winx64安装配置方法图文教程
这篇文章主要为大家详细介绍了win10下mysql 8.0.16 winx64安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2019-05-05MySQL rownumber SQL生成自增长序号使用介绍
MySQL 几乎模拟了 Oracle,SQL Server等商业数据库的大部分功能,函数。但很可惜,到目前的版本(5.1.33)为止,仍没有实现ROWNUM这个功能2011-10-10mysql报错ERROR 1396 (HY000): Operation ALT
这篇文章主要给大家介绍了关于mysql报错ERROR 1396 (HY000): Operation ALTER USER failed for root@localhost的解决方式,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下2024-05-05
最新评论