1.1 锁的特点
根据锁的特性可分为 表级锁(table-level locking)、行级锁(row-level locking)、页面锁(page-level locking)
从存储引擎角度来说:
- InnoDB 既支持行级锁,也支持表级锁,默认情况下使用行级锁
- MyISAM和MEMORY采用的是表级锁
- BDB采用的是页面锁
其中:
- 表级锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突的概率最高,并发度最低
- 行级锁:开销大,加锁慢;会出现死锁;锁定力度小,发生锁冲突的概率最低,并发度最高
- 页面锁:开销和加锁时间介于表锁和行锁之间;局出现死锁;锁定力度介于表锁和行锁之间,并发度一般
1.2 MyISAM的锁问题
1.2.1 MyISAM的表锁
MySQL的表锁有两种:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
读锁不会阻塞其他用户对同一个表的读请求,但是会阻塞对同一个表的写请求,写锁会阻塞其他用户对同一个表的读和写。
MyISAM 在执行查询语句前(SELECT),会自动给涉及的所有表加读锁,在执行更新操作前(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。
手动加表锁以及释放表锁语句:
lock table table_name_1 read local,table_name_2 read local;
select ...
unlock tables;
注意:当查询语句中有别名时,需要对表名和别名同时进行锁定
1.2.2 MyISAM的锁调度
当一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一个表的写锁,这个时候写进程先获得锁,不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读请求之前,这是因为MySQL认为写请求一般比读请求要重要,这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因。因为大量的更新操作可能会造成查询操作很难获得读锁,从而可能永远阻塞。
可以有一些设置来调节MyISAM的调度行为.
1.3 InnoDB 锁问题
InnoDB和MyISAM的最大不同有两点:一是支持事务,二是采用了行级锁。
1.3.1 事务的背景知识
-
事务及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全部不执行。
- 一致性(Consistent):在事务开始和完成时,数据都必须保证一致状态,这意味着所有相关的数据规则都必须应用于事务的修改,以保证数据的完整性;事务结束时,所有的内部数据结构也都必须是正确的。
- 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行,这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
- 持久性(Durable):事务完成之后,它对于数据的修改是永久的,即使出现系统故障也能够保持。
-
并发事务处理带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
- 更新丢失(Lost Update): 多个事务操作同一行时,最后的更新覆盖了其他事务的更新。
- 脏读(Dirty Read):一个事务读到了其他事务没有提交的数据。
- 不可重复读(Non-Repeatable Reads): 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其独处的数据已经发生了改变或某些记录已经别删除。
- 幻读(Phantom Reads): 一个事务按相同的查询条件重新读取读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据。
其中“脏读”,“不可重复读” 和 “幻读”,其实都是数据库的读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为以下两种。
- 一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
- 另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取,从用户的角度来看,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制 (MultiVersion Concurrency Control,简称MVCC),也经常成为多版本数据库。
4 种隔离级别比较:
隔离级别以及可能的情况 读数据一致性 脏读 不可重复读 幻读 读未提交 最低级别,只能保证不读取物理上损坏的数据 是 是 是 读已提交 语句级 否 是 是 可重复读 事务级 否 否 是 序列化 最高级别,事无级 否 否 否 InnoDB 和 XtraDB引擎通过多版本并发控制 解决了幻读的问题。
可重复读是 Mysql 的默认事务隔离级别
1.3.2 行锁
InnoDB实现了以下两种类型的行锁。
-
共享锁(读锁):多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能写。
select *from table_name where ... lock in share mode. -
排他锁(写锁):如果一个事务获得了一个数据航的排他锁,其他事务不能读也不能写。
select *from table_name where ... for update.
1.3.2.1 InnoDB 行锁的实现方式
InnoDB 行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。InnoDB 行锁分为三种情况。
- Record lock: 对索引项加锁
- Gap lock : 对索引项之间的 “间隙”、第一条记录前的 “间隙”或最后一条记录后的 “间隙”加锁
- Next-key lock: 前两种的组合、对记录及其前面的间隙加锁
InnoDB这种行锁实现特点意味着:
- 如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果和表锁一样!
- 由于mysql的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,时会出现锁冲突的。
- 当表有多个索引时,不同的事务可以使用不同的索引锁定不同的行,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁
注意:InnoDB除了通过范围条件加锁时使用 Next-Key锁外,如果使用相等条件请求一个不存在的记录加锁,InnoDB也会使用Next-Key锁!