对于MyISAM/Memory/Merge等引擎,mysql只提供表级锁,分为读锁和写锁,可通过lock tables手工获取,而DML会自动获取写锁;
下面着重介绍lock tables的用法
所需权限
用户必须拥有lock tables和select权限;
选项
Read [local]—可由多个会话同时获取,local用于MyISAM,允许并发插入
[low_priority] write—阻止其他会话访问该表,通常写锁比读锁优先级要高,如果某表已经被添加读锁,此时其他会话申请写锁,则所有后续读锁请求会阻塞直至写锁释放,这样会影响读锁并发;
而low_priority则允许其他读锁优先执行,使用该选项必须谨慎,很容易导致写锁饥饿等待;
对于autocommit=0的innodb表,low_priority选项无效,视为普通写锁,会导致后续读锁请求等待
算法
1 对候选基表采用内部算法排序,用户无法干预此行为
2 如果表同时请求读锁和写锁,优先处理写锁,除非使用了low_priority,但此选项对开启事务的innodb无效
3 一次只锁定一个表,但释放时同时执行
释放表锁
1 调用unlock tables,必须由执行Lock tables的同一会话执行
2 若会话再次调用lock tables,则其原本获取到的锁会被隐式释放
3 开启事务会隐含调用unlock tables,诸如start transaction
4 占有锁的会话一旦终止其锁会被自动释放,事务也会被回滚
5 rollback不会释放表锁
表锁的限制
可用于锁定视图,即锁定该视图所有基表;
表锁仅阻止其他会话进行读写操作,若当前会话拥有某个表的锁,可执行drop table,但不可执行truncate table;
当对表进行insert delayed时不可对其添加表锁,否则insert delayed会报错,只因该insert由一个单独线程操作;
所有的单个update语句会默认获取表锁;
如要对一系列MyISAM表进行大量操作,可提前锁定表以提升性能,因为Mysql对加锁的表不会刷新其key cache;
默认写锁优先级较高,但可通过low_priority_updates/max_write_lock_count/high_priority等手段提升读锁优先级,也可修改mysys/thr_lock代码只使用一个队列,让写锁/读锁拥有相同优先级;
对于非事务引擎表,因为不支持select … for update,必须先锁定表,然后查询并更新
LOCK TABLES trans READ, customer WRITE;
SELECT SUM(value) FROM trans WHERE customer_id=some_id;
UPDATE customer
SET total_value=sum_from_previous_statement
WHERE customer_id=some_id;
UNLOCK TABLES
必须一次锁定所有要访问的表,否则会抱错,而information_schema下的表不需要显示锁定也可访问
mysql> LOCK TABLES t1 READ;
mysql> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
| 3 |
+----------+
mysql> SELECT COUNT(*) FROM t2;
ERROR 1100 (HY000): Table 't2' was not locked with LOCK TABLES
不能在一条sql中引用2次被锁定的表,即便使用也必须改用alias别名
mysql> LOCK TABLE t WRITE, t AS t1 READ;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1
表锁与事务
Lock tables会隐式提交当前事务,unlock tables只有在已经调用lock tables的前提下才会隐式commit;
开启事务(诸如start transaction)会释放当前获取的表锁;
Flush tables with read lock获取的是全局锁,而非表锁;
表锁与InnoDB
对于Innodb表,若要使用表锁,必须先设置autocommit=0且innodb_table_locks=1(默认),否则InnoDB无法侦测表锁而Mysql也无法感知行锁的存在;
且在事务提交后再unlock tables,如下例所示:
调用lock tables时,innodb引擎会获取内部表锁,然后mysql服务器获取表锁;commit时innodb释放内部表锁,unlock tables时mysql服务器才释放表锁;
如果autocommit=1,innodb不会获取内部表锁,极易导致死锁发生;
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES
Lock tables与触发器
如果基表上建有触发器,且触发器引用到了其他表,则锁定基表时会连带锁定这些被引用的表
LOCK TABLES t1 WRITE, t2 READ;
CREATE TRIGGER t1_a_ins AFTER INSERT ON t1 FOR EACH ROW
BEGIN
UPDATE t4 SET count = count+1
WHERE id = NEW.id AND EXISTS (SELECT a FROM t3);
INSERT INTO t2 VALUES(1, 2);
END;
--t1添加写锁
--t2 添加写锁,即便是申请的读锁,因为在trigger有insert操作,故被转换为写锁
--t3读锁
--t4写锁
表锁与分区表
对于MyISAM引擎,DML和lock tables会锁定整个分区表,create view/alter table只会锁住使用的分区,5.6.6引入了partition lock prunning改善了此功能;
而InnoDB,在分区修剪前不会实施行级锁,故不存在锁定整个分区的问题;
表锁统计信息
mysql> SHOW STATUS LIKE 'Table%';
+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+