前面几节我们一直在介绍InnoDB的事务和行锁,这节我们来看下MyISAM的锁机制。
MyISAM存储引擎只支持表锁,并且不支持事务,也就没有什么隔离级别,所以MyISAM的表锁理解起来要简单多。
表锁是MySQL开始几个版本中唯一支持的锁类型。随着应用对事务完整性和并发性要求的不断提高,MySQL才开始开发基于事务的存储引擎,后来慢慢出现了支持页锁的BDB存储引擎和支持行锁的InnoDB存储引擎(实际 InnoDB是单独的一个公司,现在已经被Oracle公司收购)。但是MyISAM的表锁依然是使用最为广泛的锁类型。
MySQL表级锁的锁模式:
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock),和行锁中的共享锁(S锁)和排他锁(X锁)差不多的功能。表锁模式的兼容性如下表所示:
分析:
对MyISAM表的读操作,不会阻塞其他session对同一表的读请求,但会阻塞对同一表的写请求;
对MyISAM表的写操作,则会阻塞其他session对同一表的读和写操作;
MyISAM表的读操作与写操作之间,以及写操作之间是串行的!
当一个session获得对一个表的写锁后,只有持有锁的session可以对表进行更新操作。其他session的读、写操作都会等待,直到锁被释放为止。看示例:
1)(左边)1 select没问题正常显示,然后(右边)2锁表,(左边)3再次select就会一直出现锁等待状态,(右边)可以随意操作数据库。
2)(右边)6解锁,(左边)7 select正常返回结果。
MySQL如何加表锁:
加读锁:lock table 表名 read;
加写锁:lock table 表名 write;
解锁:unlock table;
table也可以写成tables
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁;
在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁。
所以都不需要用户直接用LOCK TABLE命令给MyISAM表显式加锁。我们的示例中显式加锁是为了方便做演示。
显示加锁:
给MyISAM表显示加锁,可以让MyISAM表在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。
例如,有一个订单表orders,每个订单记录有总金额total;还有一个订单明细表order_detail,每个记录中有各订单每一产品的金额小计 subtotal。假设我们需要检查这两个表的金额合计是否相符,可能就需要执行如下两条SQL:
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
这时,如果不先给两个表加锁,就可能产生错误的结果,因为第一条语句执行过程中,order_detail表可能已经发生了改变。因此,正确的方法应该是:
Lock tables orders read, order_detail read;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;
正常设计下,楠神会把订单表设计成InnoDB表。
有个地方说明下:
在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且(MyISAM)不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。演示:
1)(左边)1 开始加读锁,(右边)2 select不受影响。
2)(左边)3也能读,但4 不能访问其他表,(右边)5可访问其他表。
3)(左边)6不可以更新,7不可以插入。
其实,在自动加锁的情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因。
注意的地方:
假如表用的是别名:
这个地方知道就行,别名也需要加锁。
即用别名,又用原名,两个都要加锁。
MyISAM表锁总结:
MyISAM中执行SQL语句前自动加表锁,这条SQL语句执行完会立马解锁。
MyISAM不支持事务,也就没有回滚机制,数据插入修改出错了不能后悔,对数据的安全、严谨性要求高的项目应用不适合用MyISAM。
MyISAM没有“当前读”和“快照读”之分,只要表被获取了“写锁”,其他的session无法读数据。所以,MyISAM不适合做频繁修改数据的表,并发性差,适合做以读为主的表。
MyISAM不会产生死锁的现象。
查询表级锁争用情况:
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁争夺:
Table_locks_immediate表示立即释放表锁数,Table_locks_waited表示需要等待的表锁数,如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况,这需要MySQL做下优化了,比如更换InnoDB引擎或者把大表分成几个小表。
InnoDB使用表锁:
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。
第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁的方式和MyISAM是一样的,就不再做演示了。