乐观锁,悲观锁
是一种思想,并不是真正的mysql中提供的锁机制
悲观锁,就是对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。
悲观锁的实现,通常依靠数据库提供的锁机制实现。
乐观锁, 就是对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。
如果发现冲突了,则返回错误信息给用户,让用户自已决定如何操作。
乐观锁的实现不依靠数据库提供的锁机制,需要业务自已实现,实现方式一般是记录数据版本。一种是通过版本号,一种是通过时间戳。
共享锁, 排他锁
共享锁(读锁S):其他事务可以读,但不能写。
排他锁(写锁X) :其他事务不能读取,也不能写。
UPDATE、 DELETE 和 INSERT innodb会自动加排他锁
select 默认不加锁
select ... for update 加排他锁
select ... lock in share mode 加共享锁
意向锁
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
行锁,表锁,页面锁
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低,MyISAM 和 MEMORY 存储引擎采用的是表级锁。
行级锁: 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。InnoDB 存储引擎默认使用行级锁。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
InnoDB 行锁实现方式
InnoDB 行锁是通过给索引上的索引项加锁来实现的,所以只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁。
由于 InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然多个session是访问不同行的记录, 但是如果是使用相同的索引键, 是会出现锁冲突的。
当前读,快照读
当前读就是读最新的数据,会上行级别的排它锁。
快照读,纯select则为快照读,快照并不是指对库/表/行做拷贝,而是通过undo log记录数据的修改信息,读的时候顺着 undo log 链往前找,即不断地通过 undo log 计算出上一版本的数据,判断是否对当前快照读可见,如果不可见,再接着往前找。
快照读也是,RR 隔离级别下解决了不可重复读的解决办法。
间隙锁
在Read Repeatable隔离级别,并且在索引字段上才生效
由于InnoDB在处理行数据上是在索引上加锁,当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
间隙所可以防止幻读,以满足相关隔离级别的要求。
间隙锁的坑,在非唯一索引上,容易产生死锁,比如
假设我现在有现在一张表,id 为主键列(非唯一)
当我们在 RR 级别下执行 SELECT * from people where id = 3 for update;,实际上会给 (1, 3)加间隙锁,3 加行写锁,(3, 5)加间隙锁,这样就防止其它id为3的数据插入
而对于唯一索引来说,在进行等值查询并且匹配上的时候,间隙锁能够被优化成行锁
在范围查询修改上会有间隙锁
参考:
https://juejin.cn/post/6844903666332368909
https://zhuanlan.zhihu.com/p/29150809/