目的:解决并发、数据安全的问题
- 对于
UPDATE、DELETE、INSERT
语句,InnoDB会自动给涉及数据集加排他锁(X锁)- MyISAM在执行查询语句
SELECT
前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT
等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预 (MyISAM只有表锁)作者:Java3y 链接:https://juejin.cn/post/6844903645125820424 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分类
按粒度
- 表级锁(粒度最大)
- 整个表加锁
- 实现简单,资源消耗少
- 加锁快,无死锁
- 触发锁冲突可能性大,并发度低
- InnoDB、MyISAM都支持
- 使用场景
- 更新表中大部分数据使用表级锁效率高
- 事务比较复杂,使用行级锁容易死锁导致回滚
- 行级锁(粒度最小)
- 只对当前操作行进行加锁
- 大大减少数据库操作冲突
- 并发度高,加锁慢,开销大
- 会出现死锁
- InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁:也就是说,InnoDB的行锁是基于索引的!
- InnoDB支持
- Record Lock
- 单个行记录上的锁
- 对索引项加锁,锁定符合条件的行,其他事务不能修改和删除锁
- Gap Lock
- 间隙锁,锁定一个范围,不包括记录本身
- 用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;
- 对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”
- 其他事务不能在锁定范围内插入数据,防止了别的事务新增幻影行,已经消除了幻读。
- 间隙锁只会在
Repeatable read
隔离级别下使用
- Next-Key Lock
- 锁定一个范围,包含记录本身
- Record Lock + Gap Lock的结合,解决了幻读
- Record Lock
按是否可写
表级锁和行级锁可以进一步划分为共享锁(s)和排他锁(X)。
- 共享锁(S锁)
- Share Locks ,简写为S,又称为读锁
- 其他用户可以并发读取数据,但是任何事务都不能在共享锁锁住的基础上增加排他锁,知道已经释放所有的共享锁
- 若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 排他锁(X锁)
- Exclusive Lock,简写X锁,又称为写锁
- 事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。
- 防止任何其它事务为资源加新锁,直到在事务的末尾将资源上的原始锁释放为止。
- 在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
- 区别
- 共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不 能加排他锁。获取共享锁的事务只能读数据,不能修改数据。
- 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的锁。获取排他锁的事务既能读数据,又能修改数据。
- 两个进程竞争的时候,写锁优先于读锁
意向锁
https://juejin.cn/post/6844903666332368909
- 都是
表级锁
,意向锁是一种不与行级锁冲突表级锁
- 为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在的数据表的对应意向锁。
- 作用:如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。
- 例子1:一个用户表users
- 事务 A 获取了某一行的排他锁,并未提交
- 事务 B 想要获取
users
表的共享锁(表锁)- 因为共享锁与排他锁
互斥
,所以事务 B 在视图对users
表加共享锁的时候,必须保证:- 当前没有其他事务持有 users
表的排他锁
。 - 当前没有其他事务持有 users
表中任意一行的排他锁
。
- 当前没有其他事务持有 users
- 因为共享锁与排他锁
- 为了检测是否满足第二个条件,事务 B 必须在确保
users
表不存在任何排他锁的前提下,去检测表中的每一行是否存在排他锁。很明显这是一个效率很差的做法,但是有了意向锁之后,情况就不一样了:- 意向锁相互兼容
- 上面的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!
- 继续刚才的例子:
- A获取了行6的排他锁,此时表有两把锁
- 表的意向排他锁
- 行的排他锁
- B想要获取表共享锁:
- B检测到A有表的一个意向排他锁,就知道A必然进行了某些行的排他锁,那么B被阻塞,并且不需要去检查每一行是否有排他锁。
- 如果此时C想要获得行7的排他锁,那么
- 表有一个意向排他锁,C又加了一个意向排他锁(因为意向锁不互斥)
- C进入表中看到行7没有行锁,则给行7加锁。
乐观锁 悲观锁
在Repeatable read
隔离级别下我们来考虑一个问题:
此时,用户李四的操作就丢失掉了:
- 丢失更新:一个事务的更新覆盖了其它事务的更新结果。
解决的方法:
- 使用Serializable隔离级别,事务是串行执行的!
- 乐观锁和悲观锁
- 乐观锁
- 是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新。
- 乐观锁不是数据库层面上的锁,是需要自己手动去加的锁。
- 一般我们添加一个版本字段来实现:
- 悲观锁是数据库层面加锁,都会阻塞去等待锁。
select * from xxxx for update
- 加了排他锁
- 也就是说,如果张三使用
select ... for update
,李四就无法对该条记录修改了
死锁
InnoDB的行锁基于索引实现,如果查询没有命中任何索引,那么InnoDB使用表级锁。
InnoDB的行级锁是针对索引加的锁,不针对数据记录,因此即使访问不同行,如果使用了相同的索引键也会锁冲突。
不同于MyISAM总是一次性获得所需的全部锁,**InnoDB的锁是逐步获得的**,当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。
发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务,我们可以采取以上方式避免死锁:
- 通过**表级锁**来减少死锁产生的概率;
- 多个程序尽量约定以**相同的顺序**访问表(这也是解决并发理论中哲学家就餐问题的一种思路);
- 同一个事务尽可能做到**一次锁定所需要的所有资源**。
InnoDB VS MyISAM的区别
- InnoDB行锁和表锁都支持!
- MyISAM只支持表锁!
InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁
- 也就是说,InnoDB的行锁是基于索引的!
- Innodb支持事务,MyIsAM不支持
- InnoDB支持外键,而MyISAM不支持
- InnoDB是聚集索引,MyISAM使用的是非聚集索引
- InnoDB 不保存表的具体行数,MyISAM 用一个变量保存了整个表的行数
- InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。
隔离机制
MVCC
MVCC(Multi-Version Concurrency Control)多版本并发控制
- 可以简单地认为:MVCC就是行级锁的一个变种(升级版)。