锁的类型
- MySQL的锁分为:行级锁、表级锁、页级锁
- MyISAM 和 Memory 存储引擎使用的是表级锁
- InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁
- 表级锁,它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。
- 行级锁,它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。
- 页级锁,它是锁住的一个页面,在 InnoDB 中一个页面为16KB,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁的粒度也介于表级锁和行级锁中间,并发度也介于表级锁和行级锁中间。
- 仅仅从锁的角度来说,表级锁更加适合于以查询为主的应用,只有少量按照索引条件更新数据的应用,比如大多数的 web 应用。
- 行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用,比如一些在线事务处理系统,即 OLTP。
innoDB中的锁
- InnoDB 与 MyISAM 的相当大的两点不同在于: (1) 支持事务 (2) 采用行级锁
- 数据库实现事务隔离的方式,基本可以分为两种:
(1) 在操纵数据之前,先对其加锁,防止其他事务对数据进行修改。
这就需要各个事务串行操作才可以实现。
(2) 不加任何锁,通过生成一系列特定请求时间点的一致性数据快照,并通过这个快照来提供一致性读取。
即mvcc(Multi Version Concurrency Control),也叫多版本并发控制
- 数据库的事务隔离越严格,并发的副作用就越小,当然付出的代价也就越大,因为事务隔离机制实质上是使得事务在一定程度上”串行化”,这与并行是矛盾的。
innoDB中锁的类型
- InnoDB 实现了下面两种类型的锁:
(1)共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
(2)排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享读锁和排他写锁。
- 锁兼容和冲突的概念:如果在加一个锁的时候,另一个锁可以加上去,那么就是锁兼容。如果加上一个锁之后,拒绝其他的锁加上,那么就是锁冲突。
- 如果一个事务请求的锁模式与当前的锁兼容,InnoDB 就将请求的锁授予该事务,如果两者是冲突的,那么该事务就要等待锁释放。
- 对于 update、delete、insert 语句,InnoDB 会自动给设计到的数据集加排他锁即 X。
- 对于 select 语句,InnoDB 不会加任何锁。
- 可以使用如下语句来显式的给数据集加锁:
(1)共享锁(S):select * from t1 where ... lock in share mode;
(2)排他锁(X):select * from t1 where ... for update;
死锁的产生与避免死锁的方法
死锁<DeadLock>:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB。
死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
那么对应的解决死锁问题的关键就是:让不同的session加锁有次序
尽可能的避免事务死锁:
- 以固定的顺序访问表和行。简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁。
- 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
- 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
- 为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
乐观锁与悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
mvcc(多版本并发控制)实现乐观锁
多版本并发控制 (Multiversion concurrency control, MCC 或 MVCC),是数据库管理系统常用的一种并发控制,也用于程序设计语言实现事务内存。
MVCC 的出现就是数据库不满用悲观锁去解决读 - 写冲突问题,因性能不高而提出的解决方案。
MVCC 的实现,是通过保存数据在某个时间点的快照来实现的。每个事务读到的数据项都是一个历史快照,被称为快照读,不同于当前读的是快照读读到的数据可能不是最新的,但是快照隔离能使得在整个事务看到的数据都是它启动时的数据状态。而写操作不覆盖已有数据项,而是创建一个新的版本,直至所在事务提交时才变为可见。
当前读和快照读
当前读:像 select lock in share mode (共享锁), select for update ; update, insert ,delete (排他锁) 这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读:像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是未提交读和串行化级别,因为未提交读总是读取最新的数据行,而不是符合当前事务版本的数据行。而串行化则会对所有读取的行都加锁
mvcc的优缺点
MVCC 使大多数读操作都可以不用加锁,这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
间隙锁与行锁升级为表锁
间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(NEXT-KEY)锁。
危害:因为Query执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定值范围内的任何数据,在某些场景下这可能会针对性造成很大的危害。
行锁升级为表锁
行锁是建立在索引的基础上,如果行锁查询条件所在的列不是索引列则会升级为表锁。
普通索引的数据重复率过高导致索引失效,行锁升级为表锁。当要进行加锁的数据不确定时,也一样会是表锁(比如where条件中使用between)。