MySQL锁总结与详解

  

锁概述

MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。

最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁。

表锁就是把整张表锁起来,特点是加锁快,开销小,不会出现死锁,锁粒度大,发生锁冲突的概率高,并发相对较低。
行锁就是以行为单位把数据锁起来,特点是加锁慢,开销大,会出现死锁,锁粒度小,发生锁冲突的概率低,并发度也相对表锁较高。

MyISAM锁
MyISAM的锁调度

在MyISAM引擎中,读锁和写锁是互斥的,读写操作是串行的,锁设计方案如下:

对于写操作:如果表上没有锁,则在上面加一把写锁,否则,把请求放到写锁队列中。
对于读操作:如果表上没有锁,则在上面加一把读锁,否则,把请求方到读锁队列中。

这是什么意思呢?

意思就是说MyISAM在执行查询语句前,会自动给涉及的所有表加读锁,在执行更新语句(增删改操作)前,会自动给涉及的表加写锁,这个过程并不需要用户干预。

当一个锁被释放时,锁定权会先被写锁队列中的线程得到,当写锁队列中的请求都跑完后,才轮到读锁队列中的请求。(即使读请求先到锁等待队列中,写请求后到,写请求也会插入到读请求之前!这就是MySQL认为写请求一般比读请求重要)

这就意味着,如果一个表上有很多更新操作,那么选择语句将等待直到别的更新都结束后才能查到东西。这也就是为什么MyISAM表不适合大量更新操作应用的原因,因为大量更新操作可能导致查询操作很难获得读锁,从而长久阻塞,致使程序响应超时。

也许你需要显式加锁

 MySQL锁总结与详解

表锁语句有如下三条(MyISAM和InnoDB都一样):

;加读锁,其他会话可读,但不能更新。
;加写错,其他会话不可读,不可写。
;释放锁

当有连续多表更新的时候,可能会出现频繁的表锁竞争,更新数据的速度反而会下降,并且更新这个表的时候另一个表的数据可能被别的线程更新了(MyISAM是没有事务的),这个时候,我们就需要锁住多张表,再进行更新。

这里示例,同时上锁更新两个表,给id为1的用户余额加1:

  LOCK TABLES tb_1 WRITE,tb_2 WRITE;
  UPDATE tb_1 SET balance=balance+1 WHERE user_id=1;
  UPDATE tb_2 SET balance=balance+1 WHERE user_id=1;
  UNLOCK TABLES;

特别注意:显式加锁的时候,必须同时取得所有涉及表的锁,并且,只能访问显式加锁的这些表,不能访问未加锁的表。

(MyISAM的内容就这一章,接下来的章节都是InnDB的了,特此说明哈。)

InnoDB锁类型
共享锁(S锁、读锁)

  SELECT * FROM tb_name LOCK IN SHARE MODE;

  一个事务获取了一个数据行的读锁,允许其他事务也来获取读锁,但是不允许其他事务来获取写锁。也就是说,我上了读锁之后,其他事务也可以来读,但是不能增删改。

排他锁(X锁、写锁)

  SELECT * FROM tb_name FOR UPDATE;

  一个事务获取了一个数据行的写锁,其他事务就不能再跑来获取任何锁了,所有请求都会被阻塞,直到当前的写锁被释放。

意向锁与MDL锁

  :事务在给一个数据行加共享锁之前必须先取得该表的IS锁。
  :事务在给一个数据行加共享锁之前必须先取得该表的IX锁。
  :在事务中,InnoDB会给涉及的所有表加上一个MDL锁,其他事务就不可以执行任何DDL语句的操作。(亲测只要在事务中,不管是查询语句还是更新语句,涉及到的表都会被加上MDL锁)

  这三种锁,是InnoDB内部使用的锁,是自动实现的,不需要用户干预。

几种行锁技术
记录锁(record lock)

  这是一个索引记录锁,它是建立在索引记录上的锁(主键和唯一索引都算),很多时候,锁定一条数据,由于无索引,往往会导致整个表被锁住,建立合适的索引可以防止扫描整个表。

  如:开两个会话,两个事务,并且都不commit,该表有主键,两个会话修改同一条数据,第一个会话update执行后,第二个会话的update是无法执行成功的,会进入等待状态,但是如果update别的数据行就可以成功。

  再例如:开两个会话,两个事务,并且都不commit,并且该表无主键无索引,那么第二个会话不管改什么都会进入等待状态。因为无索引的话,整个表的数据都被第一个会话锁定了。

MySQL锁总结与详解