mysql数据库的锁
1 数据库的锁的使用场景
有如图所示场景,在并发情况下,如果不给数据库加锁,可能导致数据库的数据先被改为2000,再被改为1000,因此当用户A操作时,给数据库上锁,等用户A操作完成再给用户B操作
2 数据库有哪些锁
2.1 悲观锁
2.1.1 什么是悲观锁
悲观锁使用了mysql的锁机制,属于物理锁,都会导致操作阻塞,如果锁了事务,建议同一批次的事务的sql语句越少越好(粒度越细越好),因为这样才能更快的释放锁,因此要加悲观锁时,建议把事务拆分
在使用悲观锁时,查询数据必须按照索引来查询,由索引查询只是锁住行(形成行级锁,其他行不受影响),非索引查询锁住整张表(表级锁,整张表只能由一个线程操作),如果发生死锁,后者的严重性更大
实际开发中,锁的应用场景要很谨慎,一个不慎就会把库搞死
2.1.2 共享锁
在mysql中,使用lock in share mode实现共享锁,所谓共享锁就是每一个线程都可以见,并且可以同时加锁,但是,上一个锁没有释放前(所在线程A没有执行完毕前),其他线程无法执行修改操作或者加锁查询,只能执行不加锁的查询
因为可以重复加锁,在高并发场景会导致死锁,所以共享锁只适合用来查询,不适合用来修改
示例sql:
BEGIN; #事务开始
#加锁
select * from tb_salary WHERE salaryId = 9 LOCK IN SHARE MODE;
#SQL语句 修改数据
UPDATE tb_salary s SET salary = 8888 WHERE salaryId=8
COMMIT; #事务提交
ROLLBACK; #事务回滚
演示共享锁的特性:
执行1、4、6行代码,此时可以虽然还没有进行事务提交,但是可以看到效果
然后为了模拟并发状态,新建一个标签页,也编写同样的sql语句,也执行1、4、6行代码,可以发现没有执行成功,卡在了查询语句上,标签上一直显示“正在处理”
现在回到中间的标签页,执行事务提交,可以看到右边的标签页从“正在处理”变回了“无标题”
同时右边的标签页返回了查询结果,这意味着“被阻塞”的sql语句得到了执行
2.1.3 排它锁
和共享锁相对的就是排它锁,使用for update 对数据加排它锁,在排它锁的拥有者完成所有操作前,其他线程不能查也不能改被锁住的行或者表,也不能加锁
排它锁不会导致死锁
示例sql:
select * from tb_salary WHERE salaryId = 9 FOR UPDATE
2.2 乐观锁
2.2.1 什么是乐观锁
乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能
不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。
2.2.2 设置版本字段实现锁的功能
给数据加版本字段,connection查询数据库时获取数据的版本字段,修改时数据版本也作为条件之一,检测版本是否变化,如果变化,则此次修改失败并重试;如果无变化,则修改成功并令数据版本+1
2.2.3 将判断写在修改语句中实现锁的功能
假如用户取钱,要求取钱金额不能大于账户余额,此时可以把用户取款后余额大于0作为条件加入语句中,修改操作的伪代码如下
UPDATE tb_salary tb SET tb.salary = tb.salary - 取出金额 WHERE tb.salaryId = 8 AND tb.salary - 取出金额 > 0
3 锁的目的 数据和预期达到一致性
锁的目的是令数据的查询与修改满足预期,也就是实现一致性,而一致性又可以分为下面三种:
3.1 强一致性
定义:
在数据库中通过事务解决一致性的问题,成功则提交,失败则回滚,为了保证系统的性能,要降低事务粒度,对事务进行多次拆分
场景:
比如取款,一旦取出就要立刻达成一致,避免超额,实现手段是使用锁
3.2 弱一致性
定义:
允许数据在多少秒内最终一致
场景:
用户付款送礼时,要完成送礼、提示、增加经验、升级,假设这一整套完成要500毫秒,如果真的要一整套一整套执行,会导致用户感觉卡顿,这会影响用户体验,也会影响常见的“连击送礼”效果,所以常见套路是用户一旦花钱,立马刷新提示支付成功跳出效果,一定要快,至于增加经验、升级等操作,可以放在线程里慢一步完成
3.3 最终一致性
定义:
允许业务在XX分钟之内最终一致——使用消息队列实现
场景:
12306购票,用户下单后,要过几分钟才能确认是否购买成功,因为网站访问量太大了,所以要减少操作频率