数据库锁机制
数据库为什么要锁机制?锁机制有哪一些?
【为什么要锁】
数据库是一个多用户使用的共享资源,比如一个用户表t_user,两个浏览器前面的人登录了同个一个账号,把电话号码改了。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性(脏读,不可重复读,幻读等),可能产生死锁。为了解决这个问题,加锁是一个非常重要的技术,对实现数据库并发控制是一个好的方案。简单说,当一个执行sql语句的事务想要操作表记录之前,先向数据库发出请求,对你访问的记录集加锁,在这个事务释放这个锁之前,其他事务不能对这些数据进行更新操作。
【有哪些锁】
锁包括行级锁、表级锁、悲观锁、乐观锁
行级锁:一种它锁,防止另外事务修改此行;在使用以下语句时,Oracle会自动应用行级锁:INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];SELECT … FOR UPDATE语句允许用户一次锁定多条记录进行更新.使用commit或者rollback释放锁。MySql的innodb存储引擎默认是行级锁。特点:开锁大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。适合于有大量按索引更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理系统。
表级锁:5种
行共享 (ROW SHARE) – 禁止排他锁定表,与行排他类似,区别是别的事务还可以在此表上加任何排他锁。(除排他(exclusive)外)
行排他(ROW EXCLUSIVE) – 禁止使用排他锁和共享锁,其他事务依然可以并发地对相同数据表执行查询,插入,更新,删除操作,或对表内数据行加锁的操作,但不能有其他的排他锁(自身是可以的,没发现有什么用)
共享锁(SHARE) - 锁定表,对记录只读不写,多个用户可以同时在同一个表上应用此锁,在表没有被任何DML操作时,多个事务都可加锁,但只有在仅一个事务加锁的情况下只有此事务才能对表更新;当表已经被更新或者指定要更新时(select for update),任何事务都不能加此锁了。
共享行排他(SHARE ROW EXCLUSIVE) – 比共享锁更多的限制,禁止使用共享锁及更高的锁,在表没有被任何DML操作时,只有一个事务可以加锁,可以更新,书上说别的事务可以使用select for update锁定选中的数据行,可是实验后没被验证。
排他(EXCLUSIVE) – 限制最强的表锁,仅允许其他用户查询该表的行。禁止修改和锁定表
行级锁和表级锁是根据锁的粒度来区分的,行记录,表都是资源,锁是作用在这些资源上的。如果粒度比较小(比如行级锁),可以增加系统的并发量但需要较大的系统开销,会影响到性能,出现死锁,,因为粒度小则操作的锁的数量会增加;如果作用在表上,粒度大,开销小,维护的锁少,不会出现死锁,但是并发是相当昂贵的,因为锁定了整个表就限制了其它事务对这个表中其他记录的访问。
悲观锁:
Pessimistic Lock正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守悲观态度,事务每次去操作数据的时候都假设有其他事务会修改需要访问的数据,所以在访问之前都要求上锁,行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。 一个典型的倚赖数据库的悲观锁调用: select * from account where name=”Erica” for update 这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
Hibernate悲欢锁实现:基于数据库锁机制
Query q=Session.createQuery("select * from t_profit where amount>10000");
q.setLockMode("Profit",LockMode.UPGRADE);//Profit是Profit类的别名
List<Profit> ps=q.list();
执行的sql:select ....from t_profit where amount>10000 for update.hibernate的悲观锁通过数据库的for update实现。
LockMode.NONE:无锁机制;
LockMode.WRITE:insert,update记录时自动获取悲观锁;
LockMode.READ在读取时自动获取悲观锁;
LockMode.UPGRADE:利用数据库的for update子句加锁;
LockMode.UPGRADE_NOWAIT:oracle特定实现,用oracle的for update nowait子句加锁
乐观锁:
Optimistic Lock,和悲欢锁相反,事务每次去操作数据之前,都假设其他事务不会修改这些需要访问的数据 ,所以 在访问之前不要求上锁,只是在进行更新修改操作的时候判断一下在访问的期间有没有其他人修改数据 了。它适用于多读的应用类型,冲突真的发生比较少的时候就比较好,这样省去了开销的开销,可以提高吞吐量;但如果是真的经常要发生冲突的,那每次还要去判断进行retry,反倒降低的性能,这个时候悲欢锁比较好。数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
它的实现大多是基于数据版本versin记录机制。举个例子:
1.利润表t_profit中有一个 version字段,当前值为1;而总资产余额字段(balance)为$10000
2.操作员A读出version=1,从总资产减除2000,10000-2000=8000.
3.A还没操作结束,此时操作员B也读出version=1,总资产减除5000,10000-5000=5000.
4.A操作完成,把version加1,修改为2,把总资产减2000后提交更新数据库,更新成功
5.B操作了,也加version加1,修改为2,把总资产减5000后提交更新数据库,此时发现version已经为2了,如B修改后加1的version一样,不满足乐观锁策略:"提交的版本必有大于记录当前的版本才能执行"。因此B的操作请求被驳回,这样就避免了B就version=1的旧数据修改的结果覆盖了A操作的结果的可能。如没有乐观锁,那A减去2000后剩余8000,但B操作的时候是用10000-5000剩余5000的,如果B的提交成功,总资产余额就是5000,但实际情况应该是8000-5000=3000的。出现总资产表记录和实际支出不一致。
Hibernate对乐观锁的实现:
<hibernate-mapping>
<class name="com.f.TProfit" table="t_profit" optimistic-lock="version"></class>
</hibernate-mapping>
二: 数据库事务处理中出现的数据不一致的情况
在多个事务并发做数据库操作的时候,如果没有有效的避免机制,就会出现种种问题。大体上有四种问题,归结如下:
1、丢失更新
如果两个事务都要更新数据库一个字段X,x=100
事务A | 事务B |
读取X=100 | 读取X=100 |
写入x=X+100 | 写入x=X+200 |
事务结束x=200 | 事务结束x=300 |
最后x=300 |
两个不同事物同时获得相同数据,然后在各自事务中同时修改了该数据,那么先提交的事务更新会被后提交事务的更新给覆盖掉,这种情况事务A的更新就被覆盖掉了、丢失了。
2、脏读(未提交读)
防止一个事务读到另一个事务还没有提交的记录。 如:
事务A | 事务B |
写入x=X+100 (x=200) | |
读取X=200 (读取了事务B未提交的数据) | |
事务回滚x=100 | |
事务结束x=100 | |
事务结束 |
事务读取了未提交的数据,事务B的回滚,导致了事务A的数据不一致,导致了事务A的脏读 !
3、不可重复读
一个事务在自己没有更新数据库数据的情况,同一个查询操作执行两次或多次的结果应该是一致的;如果不一致,就说明为不可重复读。
还是用上面的例子
事务A | 事务B |
读取X=100 | 读取X=100 |
读取X=100 | 写入x=X+100 |
事务结束, x=200 | |
读取X=200 (此时,在同一个事务A中,读取的X值发生了变化!) |
|
事务结束 |
这种情况事务A多次读取x的结果出现了不一致,即为不可重复读 。
4 幻读(Phantom Read)
事务A读的时候读出了15条记录,事务B在事务A执行的过程中 增加 了1条,事务A再读的时候就变成了 16 条,这种情况就叫做幻影读。
不可重复读说明了做数据库读操作的时候可能会出现的问题。
三:事务隔离级别通过锁的实现机制
两个锁:
排他锁 被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象
共享锁 被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。
特别的,对共享锁: 如果两个事务对同一个资源上了共享锁,事务A 想更新该数据,那么它必须等待 事务B 释放其共享锁。
在运用 排他锁 和 共享锁 对数据对象加锁时,还需要约定一些规则,例如何时申请 排他锁 或 共享锁、持锁时间、何时释放等。称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。
1、一级封锁协议 (对应 read uncommited)
一级封锁协议是:事务 在对需要修改的数据上面(就是在发生修改的瞬间) 对其加共享锁(其他事务不能更改,但是可以读取-导致“脏读”),直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
一级封锁协议不能避免 丢失更新,脏读,不可重复读,幻读!
2、二级封锁协议 (对应read commited)
二级封锁协议是:1)事务 在对需要更新的数据 上(就是发生更新的瞬间) 加 排他锁 (直到事务结束) , 防止其他事务读取未提交的数据,这样,也就避免了 “脏读” 的情况。2)事务 对当前被读取的数据 上面加共享锁(当读到时加上共享锁),一旦读完该行,立即 释放该 该行的共享锁 - 从数据库的底层实现更深入的来理解,既是,数据库会对游标当前的数据上加共享锁 , 但是当游标离开当前行的时候,立即释放该行的共享锁。
二级封锁协议除防止了“脏读”数据,但是不能避免 丢失更新,不可重复读,幻读 。
但在二级封锁协议中,由于读完数据后立即 释放共享锁,所以它不能避免可重复读 ,同时它也不能避免 丢失更新 ,如果事务A、B同时获取资源X,然后事务A先发起更新记录X,那么 事务B 将等待事务 A 执行完成,然后获得记录X 的排他锁,进行更改。这样事务 A 的更新将会被丢失。 具体情况如下:
事务A | 事务B |
读取X=100(同时上共享锁) | 读取X=100(同时上共享锁) |
读取成功(释放共享锁) | 读取成功(释放共享锁) |
UPDATE X=X+100 (上排他锁) | |
UPDATING A(等待事务A释放对X的排他锁) | |
事务成功(释放排他锁)X=200 | |
UPDATE X=X+200(成功上排他锁) | |
事务成功(释放排他锁)X=300 |
由此可以看到,事务A的提交被事务B覆盖了,所以不能防止 丢失更新。
如果要避免 丢失更新,我们需要额外的操作, 对凡是读到的数据加 共享锁 和排他锁 ,这个往往需要程序员自己编程实现,比如在Oracle 中,需要加 SELECT FOR UPDATE 语句,表明,凡是该事务读到的数据,额外的加上排他锁,防止其他数据同一时间获取相同数据,这样就防止了 丢失更新 !
3、三级封锁协议 (对应reapetable read )
三级封锁协议是:二级封锁协议加上事务 在读取数据的瞬间 必须先对其加 共享锁 ,但是 直到事务结束才释放 ,这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。
三级封锁协议除防止了“脏”数据 和不可重复读 。但是这种情况不能避免 幻读 和 丢失更新 的情况,在事务 A 没有完成之前,事务 B 可以新增数据,那么 当事务 A 再次读取的时候,事务B 新增的数据会被读取到,这样,在该封锁协议下,幻读 就产生了。 如果事务A 和 事务B 同时读取了资源X=100,同样,如果事务A先对X进行 更新X=X+100,等待事务A执行完成X=200,那么事务B 获得X的排他锁,进行更新 X=X+200,然后提交 X=300,同样A的更新被B所覆盖!( 如果要避免 丢失更新,我们需要额外的操作, 对凡是读到的数据加 共享锁 和排他锁 ,这个往往需要程序员自己编程实现,比如在Oracle 中,需要加 SELECT FOR UPDATE 语句,表明,凡是读到的数据,我会加 排他锁,防止其他数据同一时间获取相同数据) !
进阶:repeatable read 导致死锁的情况(即便是 不同的资源在相同的顺序下获取)。 比如 事务1 读取 A,同时 事务2 也读取 A,那么事务1和事务2 同时对 A 上了共享锁,然后事务1 要UPDATE A,而此时 事务2 也要 UPDATE A,这个时候 事务1 等待 事务2 释放其在 A 上的共享锁,然后 事务2 要等待 事务1 释放其在 A 上的共享锁,这样,事务1 和 事务2 相互等待,产生死锁!(SQL Server/DB2 里面有 UPDATE LOCK 可以解决这种情况,具体的思路是,在 repeatable read 的情况下,将读取的数据 上的 UPDATE 锁,介于 共享锁 和 排他锁之间的一种锁,该锁的作用是 当出现上面这种情况后,事务1 和 事务2 对 A 上的是 UPDATE 锁,那么谁先 要修改 A,那么该事务就会将 UPDATE 锁可以顺利升级为 排他锁对该数据进行修改!)
4、最强封锁协议(对应Serialization)
四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读 ,都得以避免!
四:附Oracle 事务一致性原则
事务是定义和维护一致性的单位,封锁就是要保证这种一致性。如果
对封锁的要求高会增加开销,降低并发性和效率;有的事务并不严格要求
结果的质量(如用于统计的事务),如果加上严格的封锁则是不必要和不
经济的。因此有必要进行进一步的分析,考察不同级别的一致性对数据库
数据的质量及并行能力的影响。
一致性级别定义为如下的几个条件:
(1) 事务不修改其它任何事务的脏数据。脏数据是被其它事务修改过,
但尚未提交的数据。
(2) 在事务结束前不对被修改的资源解锁。
(3) 事务不读其它任何事务的脏数据。
(4) 在读前对数据加共享锁(RS)和行排它锁,直至事务结束。
* 满足条件1的事务叫第0级事务。
* 满足条件1和2的事务叫第1级一致性事务。
* 满足条件1、2和3的事务为2级一致性事务。ORACLE的读一致性保
证了事务不读其它事务的脏数据。
* 满足条件1、2、3和4的事务叫第3级一致性事务。
由ORACLE的三个性质:自动加隐式锁、在事务结束时释放锁和读一致
性,使ORACLE成为自动满足以上的0、1和2级一致性事务。因此,ORACLE
自动防止了脏读(写-读依赖)。但是,ORACLE不能自动防止丢失修改(写
-写依赖),读的不可重复性(读-写依赖),彻底解决并发性中的问题还
需满足第4个条件(3级一致性事务),这需要程序员根据实际情况编程。
方法如下:
* 如果想在一段时间内使一些数据不被其它事务改变,且在本事务内
仅仅查询数据,则可用SET TRANSACTION READ ONLY 语句达到这一
目的。
* 如果想在一事务内修改一数据,且避免丢失修改,则应在读这一数
据前用SELECT FOR UPDATE对该数据加锁。
* 如果想在一事务内读一数据,且想基于这一数据对其它数据修改,
则应在读数据前对此数据用SELECT FOR UPDATE加锁。对此种类型
的应用,用这条SQL语句加锁最恰当。
* 如果想避免不可重复读现象,可在读前用SELECT FOR UPDATE对数
据加锁,或用SET TRANSACTION READ ONLY设置只读事务。
五, 特殊情况
1) Read-Commit 的行锁导致其他事务一直被 hanging on的情况!
假设我们有 VARIANT 表, Trasaction A 对 Variant 中的字段 VariantName 1 进行了修改,但是事务未提交(假设,该事务将执行1个小时),此时 Trasaction B 要读VariantName(查询某一个VariantName 2 ),此时它会一直被Transaction A 阻塞,直到Transaction A 提交对 VariantName 的修改后,Transaction B才会得到VariantName 2 的查询结果,这样Transaction B最长可被阻塞1个小时!
这里,虽然Transaction A是针对 VariantName 1 上的修改,而 Transaction B 是读取 VariantName 2 , 对应的Variant Name不一样,但是此时,Transaction B并不知道 Transaction A 的结果(对Transaction B而言,它不清楚Transaction A提交的结果是什么),为了避免“脏读”,Transaction B会等待 Transaction A执行完事务以后,完成它对VariantName的修改后,才返回结果!
所以,在一个事务中,我们应该尽量把 SELECT Queries 放到最前面,把所有的 Update 放到最后面,避免不必要的等待!
特别的,如果上面这种情况,VariantName是Unique Index或者是Primary Key, 这个时候,Transaction B不会被Transaction A 阻塞!因为 Transaction B 知道 Transaction A提交的更改不会影响 他获取的VariantName 2 因为Transaction B 知道 VariantName 2 是唯一的!
六:MySql锁机制简介
数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。
总的来说,MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。下面我们先分析一下MySQL这三种锁定的特点和各自的优劣所在。
行级锁定(row-level)
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
表级锁定(table-level)
和行级锁定相反,表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。
页级锁定(page-level)
页级锁定是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 |
读锁定
一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件:
1、请求锁定的资源当前没有被写锁定;
2、写锁定等待队列(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的锁定特性。
总的来说,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 各事务隔离级别下锁定及死锁
Innodb实现的在ISO/ANSISQL92规范中所定义的ReadUnCommited,ReadCommited,RepeatableRead和Serializable这四种事务隔离级别。同时,为了保证数据在事务中的一致性,实现了多版本数据访问。
之前在第一节中我们已经介绍过,行级锁定肯定会带来死锁问题,Innodb也不可能例外。至于死锁的产生过程我们就不在这里详细描述了,在后面的锁定示例中会通过一个实际的例子为大家爱展示死锁的产生过程。这里我们主要介绍一下,在Innodb中当系检测到死锁产生之后是如何来处理的。
在Innodb的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。当Innodb检测到系统中产生了死锁之后,Innodb会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。那Innodb是以什么来为标准判定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在Innodb发现死锁之后,会计算出两个事务各自插入、更新或者删除的数据量来判定两个事务的大小。也就是说哪个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。但是有一点需要注意的就是,当产生死锁的场景中涉及到不止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 互相等等待对方的资源释放之后才能释放自己的资源,造成了死锁
|
合理利用锁机制优化MySQL
MyISAM 表锁优化建议
对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较到,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。
所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。
1、缩短锁定时间
缩短锁定时间,短短几个字,说起来确实听容易的,但实际做起来恐怕就并不那么简单了。如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能的短。
尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
尽可能的建立足够高效的索引,让数据检索更迅速;
尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
利用合适的机会优化MyISAM表数据文件;
2、分离能并行的操作
说到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。
3、合理利用读写优先级
在本章各种锁定分析一节中我们了解到了MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。所以,如果我们可以根据各自系统环境的差异决定读与写的优先级。如果我们的系统是一个以读为主,而且要优先保证查询性能的话,我们可以通过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读的优先级低,即可让告诉MySQL尽量先处理读请求。当然,如果我们的系统需要有限保证数据写入的性能的话,则可以不用设置low_priority_updates参数了。
这里我们完全可以利用这个特性,将concurrent_insert参数设置为1,甚至如果数据被删除的可能性很小的时候,如果对暂时性的浪费少量空间并不是特别的在乎的话,将concurrent_insert参数设置为2都可以尝试。当然,数据文件中间留有空域空间,在浪费空间的时候,还会造成在查询的时候需要读取更多的数据,所以如果删除量不是很小的话,还是建议将concurrent_insert设置为1更为合适。
Innodb 行锁优化建议
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中,以便我们后面做进一步分析使用。