为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接应想到一个数据库系统的并发处理能力和性能,因此锁定机制的实现也就成为了各类数据库的核心技术之一。本章将对MySQL中两种使用最为频繁的存储引擎MyISAM和Innodb各自的锁定机制进行较为详细的分析。html
数据库锁定机制简单来讲就是数据库为了保证数据的一致性而使各类共享资源在被并发访问访问变得有序所设计的一种规则。对于任何一种数据库来讲都须要有相应的锁定机制,因此MySQL天然也不能例外。MySQL数据库因为其自身架构的特色,存在多种数据存储引擎,每种存储引擎所针对的应用场景特色都不太同样,为了知足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,因此各存储引擎的锁定机制也有较大区别。mysql
总的来讲,MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。下面咱们先分析一下MySQL这三种锁定的特色和各自的优劣所在。算法
行级锁定最大的特色就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。因为锁定颗粒度很小,因此发生锁定资源争用的几率也最小,可以给予应用程序尽量大的并发处理能力而提升一些须要高并发应用系统的总体性能。sql
虽然可以在并发处理能力上面有较大的优点,可是行级锁定也所以带来了很多弊端。因为锁定资源的颗粒度很小,因此每次获取锁和释放锁须要作的事情也更多,带来的消耗天然也就更大了。此外,行级锁定也最容易发生死锁。数据库
和行级锁定相反,表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特色是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。因为表级锁一次会将整个表锁定,因此能够很好的避免困扰咱们的死锁问题。session
固然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的几率也会最高,导致并大度大打折扣。架构
页级锁定是MySQL中比较独特的一种锁定级别,在其余数据库管理软件中也并非太常见。页级锁定的特色是锁定颗粒度介于行级锁定与表级锁之间,因此获取锁定所须要的资源开销,以及所能提供的并发处理能力也一样是介于上面两者之间。另外,页级锁定和行级锁定同样,会发生死锁。并发
在数据库实现资源锁定的过程当中,随着锁定资源颗粒度的减少,锁定相同数据量的数据所须要消耗的内存数量是愈来愈多的,实现算法也会愈来愈复杂。不过,随着锁定资源颗粒度的减少,应用程序的访问请求遇到锁等待的可能性也会随之下降,系统总体并发度也随之提高。分布式
在MySQL数据库中,使用表级锁定的主要是MyISAM,Memory,CSV等一些非事务性存储引擎,而使用行级锁定的主要是Innodb存储引擎和NDBCluster存储引擎,页级锁定主要是BerkeleyDB存储引擎的锁定方式。高并发
MySQL的如此的锁定机制主要是因为其最初的历史所决定的。在最初,MySQL但愿设计一种彻底独立于各类存储引擎的锁定机制,并且在早期的MySQL数据库中,MySQL的存储引擎(MyISAM和Momery)的设计是创建在“任何表在同一时刻都只容许单个线程对其访问(包括读)”这样的假设之上。可是,随着MySQL的不断完善,系统的不断改进,在MySQL3.23版本开发的时候,MySQL开发人员不得不修正以前的假设。由于他们发现一个线程正在读某个表的时候,另外一个线程是能够对该表进行insert操做的,只不过只能INSERT到数据文件的最尾部。这也就是从MySQL从3.23版本开始提供的咱们所说的Concurrent Insert。
当出现Concurrent Insert以后,MySQL的开发人员不得不修改以前系统中的锁定实现功能,可是仅仅只是增长了对Concurrent Insert的支持,并无改动总体架构。但是在不久以后,随着BerkeleyDB存储引擎的引入,以前的锁定机制遇到了更大的挑战。由于BerkeleyDB存储引擎并无MyISAM和Memory存储引擎同一时刻只容许单一线程访问某一个表的限制,而是将这个单线程访问限制的颗粒度缩小到了单个page,这又一次迫使MySQL开发人员不得再也不一次修改锁定机制的实现。
因为新的存储引擎的引入,致使锁定机制不能知足要求,让MySQL的人意识到已经不可能实现一种彻底独立的知足各类存储引擎要求的锁定实现机制。若是由于锁定机制的拙劣实现而致使存储引擎的总体性能的降低,确定会严重打击存储引擎提供者的积极性,这是MySQL公司很是不肯意看到的,由于这彻底不符合MySQL的战略发展思路。因此工程师们不得不放弃了最初的设计初衷,在锁定实现机制中做出修改,容许存储引擎本身改变MySQL经过接口传入的锁定类型而自行决定该怎样锁定数据。
MySQL的表级锁定主要分为两种类型,一种是读锁定,另外一种是写锁定。在MySQL中,主要经过四个队列来维护这两种锁定:两个存放当前正在锁定中的读和写锁定信息,另外两个存放等待中的读写锁定信息,以下:
Current read-lock queue (lock->read)
Pending read-lock queue (lock->read_wait)
Current write-lock queue (lock->write)
Pending write-lock queue (lock->write_wait)
当前持有读锁的全部线程的相关信息都可以在Currentread-lockqueue中找到,队列中的信息按照获取到锁的时间依序存放。而正在等待锁定资源的信息则存放在Pendingread-lockqueue里面,另外两个存放写锁信息的队列也按照上面相同规则来存放信息。
虽然对于咱们这些使用者来讲MySQL展示出来的锁定(表锁定)只有读锁定和写锁定这两种类型,可是在MySQL内部实现中却有多达11种锁定类型,由系统中一个枚举量(thr_lock_type)定义,各值描述以下:
锁定类型 |
说明 |
IGNORE |
当发生锁请求的时候内部交互使用,在锁定结构和队列中并不会有任何信息存储 |
UNLOCK |
释放锁定请求的交互用所类型 |
READ |
普通读锁定 |
WRITE |
普通写锁定 |
READ_WITH_SHARED_LOCKS |
在Innodb中使用到,由以下方式产生如:SELECT...LOCKINSHAREMODE |
READ_HIGH_PRIORITY |
高优先级读锁定 |
READ_NO_INSERT |
不容许ConcurentInsert的锁定 |
WRITE_ALLOW_WRITE |
这个类型实际上就是当由存储引擎自行处理锁定的时候,mysqld容许其余的线程再获取读或者写锁定,由于即便资源冲突,存储引擎本身也会知道怎么来处理 |
WRITE_ALLOW_READ |
这种锁定发生在对表作DDL(ALTERTABLE...)的时候,MySQL能够容许其余线程获取读锁定,由于MySQL是经过重建整个表而后再RENAME而实现的该功能,所在整个过程原表仍然能够提供读服务 |
WRITE_CONCURRENT_INSERT |
正在进行ConcurentInsert时候所使用的锁定方式,该锁定进行的时候,除了READ_NO_INSERT以外的其余任何读锁定请求都不会被阻塞 |
WRITE_DELAYED |
在使用INSERTDELAYED时候的锁定类型 |
WRITE_LOW_PRIORITY |
显示声明的低级别锁定方式,经过设置LOW_PRIORITY_UPDAT=1而产生 |
WRITE_ONLY |
当在操做过程当中某个锁定异常中断以后系统内部须要进行CLOSETABLE操做,在这个过程当中出现的锁定类型就是WRITE_ONLY |
一个新的客户端请求在申请获取读锁定资源的时候,须要知足两个条件:
一、请求锁定的资源当前没有被写锁定;
二、写锁定等待队列(Pendingwrite-lockqueue)中没有更高优先级的写锁定等待;
若是知足了上面两个条件以后,该请求会被当即经过,并将相关的信息存入Currentread-lockqueue中,而若是上面两个条件中任何一个没有知足,都会被迫进入等待队列Pendingread-lockqueue中等待资源的释放。
当客户端请求写锁定的时候,MySQL首先检查在Currentwrite-lockqueue是否已经有锁定相同资源的信息存在。
若是Currentwrite-lockqueue没有,则再检查Pendingwrite-lockqueue,若是在Pendingwrite-lockqueue中找到了,本身也须要进入等待队列并暂停自身线程等待锁定资源。反之,若是Pendingwrite-lockqueue为空,则再检测Currentread-lockqueue,若是有锁定存在,则一样须要进入Pendingwrite-lockqueue等待。固然,也可能遇到如下这两种特殊状况:
1. 请求锁定的类型为WRITE_DELAYED;
2. 请求锁定的类型为WRITE_CONCURRENT_INSERT或者是TL_WRITE_ALLOW_WRITE,同时Currentreadlock是READ_NO_INSERT的锁定类型。
当遇到这两种特殊状况的时候,写锁定会当即得到而进入Current write-lock queue 中
若是刚开始第一次检测就Currentwrite-lockqueue中已经存在了锁定相同资源的写锁定存在,那么就只能进入等待队列等待相应资源锁定的释放了。
读请求和写等待队列中的写锁请求的优先级规则主要为如下规则决定:
1. 除了READ_HIGH_PRIORITY的读锁定以外,Pendingwrite-lockqueue中的WRITE写锁定可以阻塞全部其余的读锁定;
2. READ_HIGH_PRIORITY读锁定的请求可以阻塞全部Pendingwrite-lockqueue中的写锁定;
3. 除了WRITE写锁定以外,Pendingwrite-lockqueue中的其余任何写锁定都比读锁定的优先级低。
写锁定出如今Currentwrite-lockqueue以后,会阻塞除了如下状况下的全部其余锁定的请求:
1. 在某些存储引擎的容许下,能够容许一个WRITE_CONCURRENT_INSERT写锁定请求
2. 写锁定为WRITE_ALLOW_WRITE的时候,容许除了WRITE_ONLY以外的全部读和写锁定请求
3. 写锁定为WRITE_ALLOW_READ的时候,容许除了READ_NO_INSERT以外的全部读锁定请求
4. 写锁定为WRITE_DELAYED的时候,容许除了READ_NO_INSERT以外的全部读锁定请求
5. 写锁定为WRITE_CONCURRENT_INSERT的时候,容许除了READ_NO_INSERT以外的全部读锁定请求
随着MySQL存储引擎的不断发展,目前MySQL自身提供的锁定机制已经没有办法知足需求了,不少存储引擎都在MySQL所提供的锁定机制之上作了存储引擎本身的扩展和改造。
MyISAM存储引擎基本上能够说是对MySQL所提供的锁定机制所实现的表级锁定依赖最大的一种存储引擎了,虽然MyISAM存储引擎本身并无在自身增长其余的锁定机制,可是为了更好的支持相关特性,MySQL在原有锁定机制的基础上为了支持其ConcurrentInsert的特性而进行了相应的实现改造。
而其余几种支持事务的存储存储引擎,如Innodb,NDBCluster以及BerkeleyDB存储引擎则是让MySQL将锁定的处理直接交给存储引擎本身来处理,在MySQL中仅持有WRITE_ALLOW_WRITE类型的锁定。
因为MyISAM存储引擎使用的锁定机制彻底是由MySQL提供的表级锁定实现,因此下面咱们将以MyISAM存储引擎做为示例存储引擎,来实例演示表级锁定的一些基本特性。因为,为了让示例更加直观,我将使用显示给表加锁来演示:RITE_ALLOW_READ 类型的写锁定
时刻 |
Session a |
Session b |
行锁定基本演示 |
||
1 |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 更新,可是不提交 |
||
2 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; 被阻塞,等待 |
|
3 |
mysql> commit; Query OK, 0 rows affected (0.05 sec) 提交 |
|
4 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 0 rows affected (36.14 sec) Rows matched: 1 Changed: 0 Warnings: 0 解除阻塞,更新正常进行 |
|
无索引升级为表锁演示 |
||
5 |
mysql> update test_innodb_lock set b = '2' where b = 2000; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
mysql> update test_innodb_lock set b = '3' where b = 3000; 被阻塞,等待 |
6 |
||
7 |
mysql> commit; Query OK, 0 rows affected (0.10 sec) |
|
8 |
mysql> update test_innodb_lock set b = '3' where b = 3000; Query OK, 1 row affected (1 min 3.41 sec) Rows matched: 1 Changed: 1 Warnings: 0 阻塞解除,完成更新 |
|
间隙锁带来的插入问题演示 |
||
9 |
mysql> select * from test_innodb_lock; +------+------+ | a | b |+------+------+ | 1 | b2 | | 3 | 3 | | 4 | 4000 | | 5 | 5000 | | 6 | 6000 | | 7 | 7000 | | 8 | 8000 | | 9 | 9000 | | 1 | b1 | +------+------+ 9 rows in set (0.00 sec) mysql> update test_innodb_lock set b = a * 100 where a < 4 and a > 1; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
10 |
mysql> insert into test_innodb_lock values(2,'200'); 被阻塞,等待 |
|
11 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
12 |
mysql> insert into test_innodb_lock values(2,'200'); Query OK, 1 row affected (38.68 sec) 阻塞解除,完成插入 |
|
使用共同索引不一样数据的阻塞示例 |
||
13 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b2'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
14 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; 被阻塞 |
|
15 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
16 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; Query OK, 1 row affected (42.89 sec) Rows matched: 1 Changed: 1 Warnings: 0 session 提交事务,阻塞去除,更新完成 |
|
死锁示例 |
||
17 |
mysql> update t1 set id = 110 where id = 11; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 |
|
18 |
mysql> update t2 set id = 210 where id = 21; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
19 |
mysql>update t2 set id=2100 where id=21; 等待sessionb释放资源,被阻塞 |
|
20 |
mysql>update t1 set id=1100 where id=11; Query OK,0 rows affected (0.39sec) Rows matched: 0 Changed: 0 Warnings:0 等待sessiona释放资源,被阻塞 |
|
两个 session 互相等等待对方的资源释放以后才能释放本身的资源,形成了死锁
|
行级锁定不是MySQL本身实现的锁定方式,而是由其余存储引擎本身所实现的,如广为你们所知的Innodb存储引擎,以及MySQL的分布式存储引擎NDBCluster等都是实现了行级锁定。
考虑到行级锁定君由各个存储引擎自行实现,并且具体实现也各有差异,而Innodb是目前事务型存储引擎中使用最为普遍的存储引擎,因此这里咱们就主要分析一下Innodb的锁定特性。
总的来讲,Innodb的锁定机制和Oracle数据库有很多类似之处。Innodb的行级锁定一样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程当中为了让行级锁定和表级锁定共存,Innodb也一样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
当一个事务须要给本身须要的某个资源加锁的时候,若是遇到一个共享锁正锁定着本身须要的资源的时候,本身能够再加一个共享锁,不过不能加排他锁。可是,若是遇到本身须要锁定的资源已经被一个排他锁占有以后,则只能等待该锁定释放资源以后本身才能获取锁定资源并添加本身的锁定。而意向锁的做用就是当一个事务在须要获取资源锁定的时候,若是遇到本身须要的资源已经被排他锁占用的时候,该事务能够须要锁定行的表上面添加一个合适的意向锁。若是本身须要一个共享锁,那么就在表上面添加一个意向共享锁。而若是本身须要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁能够同时并存多个,可是意向排他锁同时只能有一个存在。因此,能够说Innodb的锁定模式实际上能够分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),咱们能够经过如下表格来总结上面这四种所的共存逻辑关系:
共享锁(S) |
排他锁(X) |
意向共享锁(IS) |
意向排他锁(IX) |
|
共享锁(S) |
兼容 |
冲突 |
兼容 |
冲突 |
排他锁(X) |
冲突 |
冲突 |
冲突 |
冲突 |
意向共享锁(IS) |
兼容 |
冲突 |
兼容 |
兼容 |
意向排他锁(IX) |
冲突 |
冲突 |
兼容 |
兼容 |
虽然Innodb的锁定机制和Oracle有很多相近的地方,可是二者的实现确是大相径庭的。总的来讲就是Oracle锁定数据是经过须要锁定的某行记录所在的物理block上的事务槽上表级锁定信息,而Innodb的锁定则是经过在指向数据记录的第一个索引键以前和最后一个索引键以后的空域空间上标记锁定信息而实现的。Innodb的这种锁定实现方式被称为“NEXT-KEYlocking”(间隙锁),由于Query执行过程当中经过过范围查找的华,他会锁定整个范围内全部的索引键值,即便这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值以后,即便某些不存在的键值也会被无辜的锁定,而形成在锁定的时候没法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能形成很大的危害。而Innodb给出的解释是为了组织幻读的出现,因此他们选择的间隙锁来实现锁定。
除了间隙锁给Innodb带来性能的负面影响以外,经过索引实现锁定的方式还存在其余几个较大的性能隐患:
当Query没法利用索引的时候,Innodb会放弃使用行级别锁定而改用表级别的锁定,形成并发性能的下降;
当Quuery使用的索引并不包含全部过滤条件的时候,数据检索使用到的索引键所只想的数据可能有部分并不属于该Query的结果集的行列,可是也会被锁定,由于间隙锁锁定的是一个范围,而不是具体的索引键;
当Query在使用索引定位数据的时候,若是使用的索引键同样但访问的数据行不一样的时候(索引只是过滤条件的一部分),同样会被锁定
Innodb实现的在ISO/ANSISQL92规范中所定义的ReadUnCommited,ReadCommited,RepeatableRead和Serializable这四种事务隔离级别。同时,为了保证数据在事务中的一致性,实现了多版本数据访问。
以前在第一节中咱们已经介绍过,行级锁定确定会带来死锁问题,Innodb也不可能例外。至于死锁的产生过程咱们就不在这里详细描述了,在后面的锁定示例中会经过一个实际的例子为你们爱展现死锁的产生过程。这里咱们主要介绍一下,在Innodb中当系检测到死锁产生以后是如何来处理的。
在Innodb的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁以后的很短期内就检测到该死锁的存在。当Innodb检测到系统中产生了死锁以后,Innodb会经过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另一个较大的事务成功完成。那Innodb是以什么来为标准断定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在Innodb发现死锁以后,会计算出两个事务各自插入、更新或者删除的数据量来断定两个事务的大小。也就是说哪一个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。可是有一点须要注意的就是,当产生死锁的场景中涉及到不止Innodb存储引擎的时候,Innodb是没办法检测到该死锁的,这时候就只能经过锁定超时限制来解决该死锁了。另外,死锁的产生过程的示例将在本节最后的Innodb锁定示例中演示。
mysql> create table test_innodb_lock (a int(11),b varchar(16)) engine=innodb; Query OK, 0 rows affected (0.02 sec) mysql> create index test_innodb_a_ind on test_innodb_lock(a); Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> create index test_innodb_lock_b_ind on test_innodb_lock(b); Query OK, 11 rows affected (0.01 sec) Records: 11 Duplicates: 0 Warnings: 0
时刻 |
Session a |
Session b |
行锁定基本演示 |
||
1 |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 更新,可是不提交 |
||
2 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; 被阻塞,等待 |
|
3 |
mysql> commit; Query OK, 0 rows affected (0.05 sec) 提交 |
|
4 |
mysql> update test_innodb_lock set b = 'b1' where a = 1; Query OK, 0 rows affected (36.14 sec) Rows matched: 1 Changed: 0 Warnings: 0 解除阻塞,更新正常进行 |
|
无索引升级为表锁演示 |
||
5 |
mysql> update test_innodb_lock set b = '2' where b = 2000; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
mysql> update test_innodb_lock set b = '3' where b = 3000; 被阻塞,等待 |
6 |
||
7 |
mysql> commit; Query OK, 0 rows affected (0.10 sec) |
|
8 |
mysql> update test_innodb_lock set b = '3' where b = 3000; Query OK, 1 row affected (1 min 3.41 sec) Rows matched: 1 Changed: 1 Warnings: 0 阻塞解除,完成更新 |
|
间隙锁带来的插入问题演示 |
||
9 |
mysql> select * from test_innodb_lock; +------+------+ | a | b |+------+------+ | 1 | b2 | | 3 | 3 | | 4 | 4000 | | 5 | 5000 | | 6 | 6000 | | 7 | 7000 | | 8 | 8000 | | 9 | 9000 | | 1 | b1 | +------+------+ 9 rows in set (0.00 sec) mysql> update test_innodb_lock set b = a * 100 where a < 4 and a > 1; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
10 |
mysql> insert into test_innodb_lock values(2,'200'); 被阻塞,等待 |
|
11 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
12 |
mysql> insert into test_innodb_lock values(2,'200'); Query OK, 1 row affected (38.68 sec) 阻塞解除,完成插入 |
|
使用共同索引不一样数据的阻塞示例 |
||
13 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b2'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
14 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; 被阻塞 |
|
15 |
mysql> commit; Query OK, 0 rows affected (0.02 sec) |
|
16 |
mysql> update test_innodb_lock set b = 'bbbbb' where a = 1 and b = 'b1'; Query OK, 1 row affected (42.89 sec) Rows matched: 1 Changed: 1 Warnings: 0 session 提交事务,阻塞去除,更新完成 |
|
死锁示例 |
||
17 |
mysql> update t1 set id = 110 where id = 11; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 |
|
18 |
mysql> update t2 set id = 210 where id = 21; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
19 |
mysql>update t2 set id=2100 where id=21; 等待sessionb释放资源,被阻塞 |
|
20 |
mysql>update t1 set id=1100 where id=11; Query OK,0 rows affected (0.39sec) Rows matched: 0 Changed: 0 Warnings:0 等待sessiona释放资源,被阻塞 |
|
两个 session 互相等等待对方的资源释放以后才能释放本身的资源,形成了死锁
|
对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程当中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定自己所消耗的资源也是最少。可是因为锁定的颗粒度比较到,因此形成锁定资源的争用状况也会比其余的锁定级别都要多,从而在较大程度上会下降并发处理能力。
因此,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提升并发度。因为锁定级别是不可能改变的了,因此咱们首先须要尽量让锁定的时间变短,而后就是让可能并发进行的操做尽量的并发。
一、缩短锁定时间
缩短锁定时间,短短几个字,提及来确实听容易的,但实际作起来恐怕就并不那么简单了。如何让锁定时间尽量的短呢?惟一的办法就是让咱们的Query执行时间尽量的短。
尽两减小大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
尽量的创建足够高效的索引,让数据检索更迅速;
尽可能让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
利用合适的机会优化MyISAM表数据文件;
二、分离能并行的操做
说到MyISAM的表锁,并且是读写互相阻塞的表锁,可能有些人会认为在MyISAM存储引擎的表上就只能是彻底的串行化,没办法再并行了。你们不要忘记了,MyISAM的存储引擎还有一个很是有用的特性,那就是ConcurrentInsert(并发插入)的特性。
MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,能够设置为0,1或者2。三个值的具体说明以下:
concurrent_insert=2,不管MyISAM存储引擎的表数据文件的中间部分是否存在由于删除数据而留下的空闲空间,都容许在数据文件尾部进行ConcurrentInsert;
concurrent_insert=1,当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,能够从文件尾部进行ConcurrentInsert;
concurrent_insert=0,不管MyISAM存储引擎的表数据文件的中间部分是否存在由于删除数据而留下的空闲空间,都不容许ConcurrentInsert。
三、合理利用读写优先级
在本章各类锁定分析一节中咱们了解到了MySQL的表级锁定对于读和写是有不一样优先级设定的,默认状况下是写优先级要大于读优先级。因此,若是咱们能够根据各自系统环境的差别决定读与写的优先级。若是咱们的系统是一个以读为主,并且要优先保证查询性能的话,咱们能够经过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读的优先级低,便可让告诉MySQL尽可能先处理读请求。固然,若是咱们的系统须要有限保证数据写入的性能的话,则能够不用设置low_priority_updates参数了。
这里咱们彻底能够利用这个特性,将concurrent_insert参数设置为1,甚至若是数据被删除的可能性很小的时候,若是对暂时性的浪费少许空间并非特别的在意的话,将concurrent_insert参数设置为2均可以尝试。固然,数据文件中间留有空域空间,在浪费空间的时候,还会形成在查询的时候须要读取更多的数据,因此若是删除量不是很小的话,仍是建议将concurrent_insert设置为1更为合适。
Innodb存储引擎因为实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,可是在总体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的总体性能和MyISAM相比就会有比较明显的优点了。可是,Innodb的行级锁定一样也有其脆弱的一面,当咱们使用不当的时候,可能会让Innodb的总体性能表现不只不能比MyISAM高,甚至可能会更差。
要想合理利用Innodb的行级锁定,作到扬长避短,咱们必须作好如下工做:
尽量让全部的数据检索都经过索引来完成,从而避免Innodb由于没法经过索引键加锁而升级为表级锁定;
合理设计索引,让Innodb在索引键上面加锁的时候尽量准确,尽量的缩小锁定范围,避免形成没必要要的锁定而影响其余Query的执行;
尽量减小基于范围的数据检索过滤条件,避免由于间隙锁带来的负面影响而锁定了不应锁定的记录;
尽可能控制事务的大小,减小锁定的资源量和锁定时间长度;
在业务环境容许的状况下,尽可能使用较低级别的事务隔离,以减小MySQL由于实现事务隔离级别所带来的附加成本;
因为Innodb的行级锁定和事务性,因此确定会产生死锁,下面是一些比较经常使用的减小死锁产生几率
的的小建议,读者朋友能够根据各自的业务特色针对性的尝试:a)相似业务模块中,尽量按照相同的访问顺序来访问,防止产生死锁;b)在同一个事务中,尽量作到一次锁定所须要的全部资源,减小死锁产生几率;c)对于很是容易产生死锁的业务部分,能够尝试使用升级锁定颗粒度,经过表级锁定来减小死锁产生的几率;
系统锁定争用状况查询对于两种锁定级别,MySQL内部有两组专门的状态变量记录系统内部锁资源争用状况,咱们先看看
MySQL 实现的表级锁定的争用状态变量:
mysql> show status like 'table%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Table_locks_immediate | 100 | | Table_locks_waited | 0 | +-----------------------+-------+
这里有两个状态变量记录MySQL内部表级锁定的状况,两个变量说明以下:
Table_locks_immediate:产生表级锁定的次数;
Table_locks_waited:出现表级锁定争用而发生等待的次数;
两个状态值都是从系统启动后开始记录,没出现一次对应的事件则数量加1。若是这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就须要进一步分析为何会有较多的锁定资源争用了。
对于Innodb所使用的行级锁定,系统中是经过另一组更为详细的状态变量来记录的,以下:
mysql>showstatuslike'innodb_row_lock%'; +-------------------------------+--------+|Variable_name|Value|+-------------------------------+--------+ |Innodb_row_lock_current_waits|0| |Innodb_row_lock_time|490578| |Innodb_row_lock_time_avg|37736| |Innodb_row_lock_time_max|121411| |Innodb_row_lock_waits|13| +-------------------------------+--------+
Innodb 的行级锁定状态变量不只记录了锁定等待次数,还记录了锁定总时长,每次平均时长,以及最大时长,此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明以下:
Innodb_row_lock_current_waits:当前正在等待锁定的数量;
Innodb_row_lock_time:从系统启动到如今锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到如今等待最常的一次所花的时间;
Innodb_row_lock_waits:系统启动后到如今总共等待的次数;
对于这5个状态变量,比较重要的主要是Innodb_row_lock_time_avg(等待平均时长),Innodb_row_lock_waits(等待总次数)以及Innodb_row_lock_time(等待总时长)这三项。尤为是当等待次数很高,并且每次等待时长也不小的时候,咱们就须要分析系统中为何会有如此多的等待,而后根据分析结果着手指定优化计划。
此外,Innodb出了提供这五个系统状态变量以外,还提供的其余更为丰富的即时状态信息供咱们分析使用。能够经过以下方法查看:
1.经过建立InnodbMonitor表来打开Innodb的monitor功能:
mysql> create table innodb_monitor(a int) engine=innodb; Query OK, 0 rows affected (0.07 sec)
2.而后经过使用“SHOWINNODBSTATUS”查看细节信息(因为输出内容太多就不在此记录了);
可能会有读者朋友问为何要先建立一个叫innodb_monitor的表呢?由于建立该表实际上就是告诉Innodb咱们开始要监控他的细节状态了,而后Innodb就会将比较详细的事务以及锁定信息记录进入MySQL的errorlog中,以便咱们后面作进一步分析使用。
本章以MySQLServer中的锁定简介开始,分析了当前MySQL中使用最为普遍的锁定方式表级锁定和行级锁定的基本实现机制,并经过MyISAM和Innodb这两大典型的存储引擎做为示例存储引擎所使用的表级锁定和行级锁定作了较为详细的分析和演示。而后,再经过分析两种锁定方式的特性,给出相应的优化建议和策略。最后了解了一下在MySQLServer中如何得到系统当前各类锁定的资源争用情况。但愿本章内容可以对各位读者朋友在理解MySQL锁定机制方面有必定的帮助。