不同范围的锁
不同范围的锁
for update
的记录不存在会导致锁住全表。当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,
mysql> select * from tab_no_index where id = 1 for update;
--- 添加索引
mysql> alter table tab_with_index add index id(id);
--- 在没有索引的情况下,InnoDB 只能使用表锁,会导致下述查询语句等待
--- 当我们给其增加一个索引后,InnoDB 就只锁定了符合条件的行,不会出现锁等待
mysql> select * from tab_no_index where id = 2 for update;
由于
mysql> select * from tab_with_index where id = 1 and name = '1' for update;
--- 虽然 session_2 访问的是和 session_1 不同的记录,但是因为使用了相同的索引,所以需要等待锁
mysql> select * from tab_with_index where id = 1 and name = '4' for update;
即便在条件中使用了索引字段,但是否使用索引来检索数据是由
对于
- 事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
- 事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
--- 写表t1并从表t读
SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;
案例:删除表的策略
如果你要删除一个表里面的前
- 第一种,直接执行
delete from T limit 10000;
,事务相对较长,则占用锁的时间较长,会导致其他客户端等待资源时间较长。 - 第二种,在一个连接中循环执行
20 次delete from T limit 500;
,串行化执行,将相对长的事务分成多次相对短的事务,则每次事务占用锁的时间相对较短,其他客户端在等待相应资源的时间也较短。这样的操作,同时也意味着将资源分片使用(每次执行使用不同片段的资源) ,可以提高并发性。 - 第三种,在
20 个连接中同时执行delete from T limit 500
,为自己制造锁竞争,加剧并发量。
锁分类
根据加锁的范围,
- 全局锁:对整个数据库实例加锁,常用于全库逻辑备份。
- 表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低。
- 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高;
- 页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。
每个存储引擎都可以有自己的锁策略,例如
行锁 | 表锁 | 页锁 | |
---|---|---|---|
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
全局锁
全局锁即对整个数据库实例加锁。
在异常处理机制上有差异。如果执行
表级锁
lock tables ... read/write
,可以用
另一类表级的锁是
行锁
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。譬如事务
间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,
举例来说,假如mysql> select * from emp where empid > 100 for update;
就是一个范围条件的检索,
很显然,在使用范围条件检索并锁定记录时,
--- 当前session对不存在的记录加for update的锁:
mysql> select * from emp where empid = 102 for update;
--- 这时,如果其他session插入empid为102的记录(注意:这条记录并不存在),也会出现锁等待:
mysql>insert into emp(empid,...) values(102,...);
--- session 1 执行 rollback:
mysql> rollback;
--- 由于其他session_1回退后释放了Next-Key锁,当前session可以获得锁并成功插入记录:
mysql>insert into emp(empid,...) values(102,...);