文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号。
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中数据其实是一种供大量用户共享的资源,所以在并发访问时我们需要保证数据的一致性和有效性,而锁冲突是影响数据库并发性能最关键的因素之一。所以本篇文章主要讨论Mysql中锁机制的特点。Mysql的锁机制包含多种:行锁,表锁,读锁,写锁等,其实就是使用不同的存储引擎会支持不同的锁机制。而我主要是针对InnoDB存储引擎下的7种类型的锁进行介绍。
InnoDB引擎锁类型:
- 共享/排它锁
- 记录锁
- 间隙锁
- 临键锁
- 自增锁
- 意向锁
- 插入意向锁
MySQL中InnoDB存储引擎与MyISAM存储引擎锁机制其实有两个比较显著的不同点:
- InnoDB支持事务操作。
- InnoDB默认采用行级锁。
InnoDB锁机制实现原理
InnoDB存储引擎其实是通过给索引上的索引项添加锁,也正是由于给索引项加锁,所以只有通过索引条件查询数据,InnoDB引擎才会选择使用行级锁,否则会使用表锁。行级锁与表级锁本身有许多不同之处,事务的引入也带来了一些新问题。
事务
说到事务,四个最基本的特性我想大多数人再清楚不过了:
- 原子性: 事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性:在事务开始和完成时,数据都必须保持一致状态。
- 隔离性:事务处理过程中的中间状态对外部是不可见的。
- 持久性:事务完成之后,它对于数据的修改是永久性的。
并发的问题
并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,但并发事务处理如果没有添加锁存在几个问题:
- 更新丢失:两个事务同时对同一个数据进行更新操作,先更新的数据会被后更新的给更换了,造成数据丢失。
- 脏读:两个事务一个负责更新操作,一个负责查询操作,更新操作更新了但是还未提交数据,这时候查询数据会出现数据不一致现象,也就是我们经常说的脏数据。
- 不可重复读:有一个事务在读取数据后的某个时间,再次读取同样的数据,却发现得到数据发生改变。
- 幻读:第一个事务查询数据,重复读取相同数据,第二次检索比第一次得到新数据,叫幻读现象。
共享/排它锁
这种锁机制实际上有两个锁:共享锁和排它锁。读取数据时会使用共享锁,是可以并行操作的,也就是读取数据操作是可以并发进行的。修改数据时会使用排它锁,排它锁与任何锁都是互斥存在,也就是修改数据是其他操作无论读取还是修改操作都不能并发进行。
记录锁
记录锁顾名思义封锁记录,例如我们查找学号sid为1的学生信息,使用排它锁就会将这行记录锁死,所以其他事务都无法对这行数据进行操作。实际上记录锁是先获取这个数据表的意向排它锁,表示本事务有意向向这个数据表的某些数据加排它锁,然后在获取这行数据的排它锁,这时候其他他事务都无法对这行数据进行操作了。那什么是意向排它锁呢,接下来我们看看意向锁。
意向锁
意向锁之所以出现实际上是为了让行级锁与表级锁共存,意向锁实际上就是刚才所讲的,本事务未来某个时刻需要对某个数据表加锁,所以先和你声明一下意向。意向锁是表级锁,一样分为意向共享锁与意向排它锁,但是和共享排它锁有一点不同是意向锁之间是互相兼容的,也就是说无论是意向排它锁还是意向共享锁之间都是相互兼容的,但是意向锁与排它锁是不兼容的,因为排它锁不与其他任何锁相兼容。
间隙锁
间隙锁的出现其实是为了杜绝不可重复读的情况,使用间隙锁可以对某个字段添加区间限制,比如我们查询id=1的数据我们可以对id为1的数据添加间隙锁,这样本事务没有结束之前,其他数据无法对id为1的数据进行更新操作,也就是可以解决不可重复读的问题。如果将事务的隔离级别设置为读提交,则间隙锁无法生效。
临键锁
临键锁实际上是同时作用于索引间隙和索引记录,也就是记录锁和间隙锁的结合体。使用临键锁默认情况下是同时对索引记录和索引间隙进行锁定,也就是双重锁定,但是如果索引为唯一索引或者主键索引,则会使用记录锁只对索引本身加锁,不会添加间隙锁,所以说字段有唯一值可以添加唯一索引提高性能。
插入意向锁
插入意向锁是作用于索引上,专门用于插入操作的锁。这个锁是可以多个事务共同操作的,多个事务同时操作同一个索引,在插入记录时如果插入位置不冲突则不会互相影响插入操作。比如我们两个事务同时进行,分别插入id为1和id为2的记录,因为id不冲突,所以不会互相阻塞操作。
自增锁
自增锁很明显是用于自增类型的操作,自增锁是表级锁,自增锁的作用是为了保证数据库的主键是自动递增的。其实这个锁主要就是用于拥有自增主键的数据表的插入操作,两个事务先后执行插入操作,第二个事务的插入操作则会被阻塞,因为需要保证主键是递增操作。
表锁注意事项
刚才其实提到了,InnoDB中默认使用行级锁,但是意向锁这种表级锁其实有时候更适合,比如需要在事务中更新大部分数据,这时候使用表锁就可以提高事务的执行任务。或者说事务中涉及多个表的操作,如果出现死锁等,就会造成大量操作回滚,所以这种情况就可以直接将所有涉及的表都添加表锁,就可以减小出现死锁的概率,减小数据库的开销。
避免死锁的方案
死锁其实就是两个事务在执行过程中因为争夺资源造成的一种互相等待的现象,这时候就成为产生了死锁。死锁产生的关键原因其实是因为两个事务加锁的顺序不一致,然后互相持有资源不释放又互相等待对方的资源。发生死锁一般情况下InnoDB引擎可以自动检测并且使一个事务回滚解决死锁问题,但是涉及表锁或者外部锁时不能保证检测到死锁的存在,所以我们需要了解避免死锁的方案。一般有以下几种策略:
- 死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,它破坏了环路等待条件。预防通常会降低系统的效率。
- 死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算法的执行会增加系统的开销。
- 死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执行死锁解除策略。
- 死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分配给其他的进程。
欢迎关注公众号:程序猿周先森。文章原创于微信公众号,本平台不定时更新。