文章目录
- mysql锁
- 隔离级别与锁
- 按锁粒度,锁有哪些?
- 按锁类别,锁有哪些?
- 分布式锁
- 乐观锁
- 悲观锁
mysql锁
隔离级别与锁
- Read Uncommitted级别下(可读到其他事务未提交的数据),读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突。
- 在Read Committed级别下(其他事务提交操作后,才可见),读取操作需要加共享锁,但是在语句执行完以后释放共享锁。
- 在Repeatable Read级别下(同一事务重复读取的数据一致性),读取操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。
- SERIALIZABLE 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。
疑问??什么是共享锁??
共享锁=共享读锁,排他锁=独占写锁
按锁粒度,锁有哪些?
锁机制与InnoDB锁算法
在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。
MyISAM采用表级锁(table-level locking)。InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。
InnoDB实现了行级锁,页级锁,表级锁。
行级锁,表级锁和页级锁对比
- 行级锁
锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。开销最大;加锁慢;会出现死锁;并发度最高
分为共享锁、排他锁。 - 表级锁
锁定粒度最大的一种锁,表示对当前操作的整张表加锁。
分为表共享读锁(共享锁)、表独占写锁(排他锁)。 - 页级锁
MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。
表级锁速度快,但冲突多;行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
按锁类别,锁有哪些?
- 共享锁
又叫做读锁。当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。 - 排他锁
又叫做写锁,当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。
行锁如何实现?
InnoDB是基于索引来完成行锁
select * from tab_with_index where id = 1 for update;
-- for update 可以根据条件来完成行锁锁定,并且 ID 是有索引键的列,如果 ID不是索引键那么InnoDB将完成表锁,并发将无从谈起
锁算法:3种
- Record lock:单个行记录上的锁
- Gap lock:间隙锁,锁定一个范围,不包括记录本身
为了阻止多个事务将记录插入到同一范围内,这会导致幻读问题。 - Next-key lock:record+gap 锁定一个范围,包含记录本身
InnoDB对于行查询采用此
何为死锁?
两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
如果业务处理不好可以用分布式事务锁或者使用乐观锁
分布式锁
先来看看功能:
- 分布式锁使用者位于不同的机器中,所获取成功后,才可以对共享资源进行操作
- 锁具有重入的功能:即一个使用者可以多次获取某个锁
- 获取锁有超时的功能:超过指定的时间,还未获取成功,则返回获取失败
- 自动容错功能:A机器获取锁lock1之后,在释放lock1之前,机器A挂掉了,导致锁lock1还未释放。结果就是lock1一直被机器A占有。分布式锁能自动解决:持有锁规定持有超时时间,超时还未释放时,其余机器将争夺该锁。
乐观锁
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过Version的方式来进行锁定。
实现方式:一般会使用版本号机制或CAS算法实现。
在修改表中数据记录过程如下:
- select获取记录R1
- 对R1进行编辑
- update更新R1
存在什么问题呢?
当A、B两个线程同时执行到步骤1,这俩获取的R1数据是一致的,然后同时执行步骤2,最后执行步骤3,最终2个线程都会更新成功。
但是后一个执行的线程会把前一个线程更新update的结果给覆盖掉,这就是并发修改数据的问题
如何解决呢?
在表中新增一个版本号,每次更新数据时将版本号作为条件,执行一次更新就版本号+1,过程优化一下,如下:
- 打开事务start transaction
- select获取记录R1,声明变量
v=R1.version
- 对R1进行编辑
- 执行更新update
update R1 set version=version+1 where user_id=#user_id# and version=#v#;
- 步骤4中更新update返回影响的行数,将其记录在count中,然后根据count来判断提交事务?回滚事务?
if(count==1){
//提交事务
commit;
}else{
//回滚事务
rollback;
}
步骤4中,当多个线程同时执行到步骤2时,获取到的记录R1是一样的,但当执行到步骤4时,数据库对update的这行数据加锁,确保并发情况下排队执行
故只有第一次执行update更新操作会返回1,其余线程的更新update操作回返回0,根据count值确定事务提交 / 回滚
所有线程执行select查询时,线程的数据v都是R1记录的版本:
v=R1.version
但经过一次更新update后,R1记录+1,但其余线程的数据v还是原始的version数据,故不可能执行。
update R1 set version=version+1 where user_id=#user_id# and version=#v#;
上面这种利用版本号的方式就是乐观锁,确保了数据并发修改过程中正确性
考虑⼀个问题:⽐如A机会获取了key1的锁,并设置持有锁的超时时间为10秒,但是获取锁之后,执⾏了⼀段业务操作,业务操作耗时超
过10秒了,此时机器B去获取锁时可以获取成功的,此时会导致A、B两个机器都获取锁成功了,都在执⾏业务操作,这种情况应该怎么处
理?
悲观锁
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。
实现方式:使用数据库中的锁机制
应用场景:
乐观锁多读,悲观锁多写