1、MyISAM表锁
MyISAM存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型。随着应用对事务完整性和并发性要求的不断提高,MySQL才开始开发基于事务的存储引擎,后来慢慢出现了支持页锁的BDB存储引擎和支持行锁的InnoDB存储引擎(实际 InnoDB是单独的一个公司,现在已经被Oracle公司收购)。但是MyISAM的表锁依然是使用最为广泛的锁类型。
1.1、MySql表级的锁模式
MySql
的表级锁有两种模式:表共享读锁(Table Read Lock)
和表独占写锁(Table Write Lock)
锁模式的兼容性
请求锁模式 是否兼容当前锁模式 | None | 读锁 | 写锁 |
---|---|---|---|
读锁 | 是 | 是 | 否 |
写锁 | 是 | 否 | 否 |
对MyISAM表的读操作
,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表
的写请求对 MyISAM表的写操作
,则会阻塞其他用户对同一表
的读和写操作MyISAM表的读操作与写操作之间,以及写操作之间是串行的!
1.2、如何加表锁
MyISAM在执行
查询语句(SELECT)
前,会自动给涉及的所有表加读锁
;在执行
更新操作(UPDATE、DELETE、INSERT等)
前,会自动给涉及的表加写锁
;这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
- 在
Lock TABLES
给表显示加表锁时,必须同时取得所有涉及到的表的锁,并且MYSQL不支持锁升级 - 在执行
Lock TABLES
后,只能访问显示加锁的表,不能访问未加锁的表 - 如果是读锁,只能执行查询操作,而不能执行更新操作
- 在自动加锁的情况下,
MYISAM
总是一次性获得SQL
语句所需要全部锁 - 当使用
Lock Table
时,不仅需要一次锁定用到的表,而且,同一个表在SQL语句中出现多少次,就要通过SQL
语句中相同的别名锁定多少次
1.3、并发插入
上文提到过MyISAM表的读和写是串行的,但这是就总体而言的。在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
- 当concurrent_insert设置为0时,不允许并发插入。
- 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
- 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
1.4、MyISAM的锁调度
前面讲过,MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?
答案是:
写进程先获得锁
不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前!
**这是因为MySQL认为写请求一般比读请求要重要。**这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!
幸好我们可以通过一些设置来调节MyISAM 的调度行为。
2、InnoDB的行锁模式及加锁模式
2.1、InnoDB中的锁
InnoDB
实现了以下两种类型的行锁。
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
2.2、锁的使用
意向锁是InnoDB自动加的,不需用户干预; 对于UPDATE
、DELETE
和INSERT
语句,InnoDB
会自动给涉及数据集加排他锁(X)
;对于普通SELECT
语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
。排他锁(X):
SELECT * FROM table_name WHERE ... FOR UPDATE
。
用
SELECT ... IN SHARE MODE
获得共享锁
,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。
2.3、InnoDB行锁实现方式
InnoDB行锁是通过索引上的
索引项
来枷锁实现的在实际应用中,需要特别注意
InnoDB
行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能
- 在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁
- 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的
- 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引
2.4、间隙锁(Next-Key锁)
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
/*举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL:*/
Select * from emp where empid > 100 for update;
/*是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。*/
InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要
很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
3、事务
3.1、事务及其ACID属性
事务是由一组
SQL
语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID
属性
ACID属性
- 原子性(Atomicitity):事务是由一个原子操作单元,其对数据的修改,要么全执行,要么全部执行
- 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须因应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引和双向链表)也都必须是正确的。
- 隔离型(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的
独立
环境执行。这意味着事务处理过程中的中间状态对外部是不可见的 - 持久性(Durable):事务完成之后,它对于数据的修改时永久性的,即使出现系统故障也能够保持。
3.2、并发事务处理带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
更新丢失
:当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新
脏读
:一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"
不可重复读:
**一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。 **
幻读
:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入
了满足其查询条件的新数据,这种现象就称为“幻读”note: 不可重复读和幻读本质上是一样的,就是一个事务内两次读取同样的数据,结果不一样;这就是统一的欢度问题;
行级锁
可以解决更新
和删除
导致的幻读
;间隙锁
可以解决插入
导致的幻读
3.3、事务的隔离级别
为了解决上述的问题:
在上面讲到的并发事务处理带来的问题中,==“更新丢失”==通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库实现事务隔离的方式,基本上可分为以下两种。
- 一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
- 另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。下表很好地概括了这4个隔离级别的特性。