MySQL锁相关知识点

MySQL 锁

  MySQL 的锁机制,主要用于高并发场景下,比较重要的知识点是保证数据的一致性的和事务隔离性,在高并发下控制并发访问。

 

 

 

锁的种类

  MySQL 中锁的分类按照不同类型的划分可以分成不同的锁,

  • 按照锁的粒度:
    • 表锁:粒度最大的锁,开销小,加锁快,不会出现死锁,但是由于粒度太大,因此造成锁的冲突几率大,并发性能低。常见的有 MyISAM 储存引擎就支持表锁,MyISAM的表锁模式有两种:表共享读锁和表独占写锁

      当一个线程获取到 MyISAM 表的读锁的时候,会阻塞其他用户对该表的写操作,但是不会阻塞其它用户对该用户的读操作。

      相反的,当一个线程获取到 MyISAM 表的写锁的时候,就会阻塞其它用户的读写操作对其它的线程具有排它性。

    • 页锁:粒度是介于行锁和表锁之间的一种锁,页锁是在 BDB 中支持的一种锁机制,很少提及和使用
    • 行锁:粒度最小的锁机制,行锁的加锁开销性能大,加锁慢,并且会出现死锁,但是行锁的锁冲突的几率低,并发性能高。

      行锁是 InnoDB 默认的支持的锁机制,MyISAM 不支持行锁,这个也是 InnoDB 和 MyISAM 的区别之一。

      行锁在使用的方式上可以划分为:共享读锁( S 锁)排它写锁( X 锁)

      当一个事务对 MySQL 中的一条数据行加上了 S 锁,当前事务不能修改该行数据只能执行读操作,其他事务只能对该行数据加 S 锁不能加 X 锁。若是一个事务对一行数据加了 X 锁,该事务能够对该行数据执行读和写操作,其它事务不能对该行数据加任何的锁,既不能读也不能写。

  • 按照使用的方式共享锁排它锁
  • 按照思想:数据库管理系统中为了控制并发,保证在多个事务执行时的数据一致性以及事务的隔离性,使用悲观锁和乐观锁来解决并发场景下的问题。
    • 乐观锁:需要程序员自己去实现的锁机制
    • 悲观锁:悲观锁的实现是基于Mysql自身的锁机制实现

悲观锁和乐观锁是在很多框架都存在的一种思想,不要狭义地认为它们是某一种框架的锁机制。

 

MyISAM

  MyISAM中 默认支持的表级锁有两种:共享读锁独占写锁。表级锁在 MyISAM 和 InnoDB 的存储引擎中都支持,但是 InnoDB 默认支持的是行锁。

  MySQL 中平时读写操作都是隐式的进行加锁和解锁操作,MySQL 已经自动实现加锁和解锁的操作,若是想要测试锁机制,就要显示的自己控制锁机制。

MySQL 中可以通过以下 sql 来显示的在事务中显式的进行加锁和解锁操作:

-- 显式的添加表级读锁
LOCK TABLE 表名 READ
-- 显示的添加表级写锁
LOCK TABLE 表名 WRITE
-- 显式的解锁(当一个事务commit的时候也会自动解锁)
unlock tables;

 

MyISAM 表级写锁

CREATE TABLE IF NOT EXISTS employee (
    id INT PRIMARY KEY auto_increment,
    name VARCHAR(40),
    money INT
)ENGINE MyISAM

INSERT INTO employee(name, money) VALUES('黎杜', 1000);
INSERT INTO employee(name, money) VALUES('非科班的科班', 2000);
示例使用的表结构及数据
LOCK TABLE employee WRITE
第一个程序使用锁
select * from employee  # 阻塞
第二个程序对同一个表进行操作时,会阻塞

第一个程序可以任意读写该表,但其他程序不行。只有在锁被释放时,别的程序才能对该表进行操作

 

MyISAM表级共享读锁

测试数据同上

LOCK TABLEemployee read;
第一个程序使用锁

第一个程序进行插入、更新数据,发现都会报错,只能查询数据。

其他程序也无法进行插入、更新数据,但是可以查询数据。

 

MyISAM表级锁竞争情况

MyISAM存储引擎中,可以通过查询变量来查看并发场景锁的争夺情况

show status like 'table_locks%';

通过 table_locks_waited 和 table_locks_immediate 的值的大小分析锁的竞争情况。

  • Table_locks_immediate:表示能够立即获得表级锁的锁请求次数;
  • Table_locks_waited表示不能立即获取表级锁而需要等待的锁请求次数分析,值越大竞争就越严重

 

并发插入

  上述例子中的加锁和释放锁都是在 MySQL 中已经实现了的隐式的操作,实际并不会这么做的。

  MyISAM 存储引擎中,虽然读写操作是串行化的,但是它也支持并发插入,这个需要设置内部变量 concurrent_insert的值。它的值有三个值0、1、2。可以通过以下的 sql 查看 concurrent_insert 的默认值为AUTO(或者1)

  • 值为 NEVER (or 0) 表示不支持比并发插入;
  • 值为 AUTO (或者1)表示在MyISAM表中没有被删除的行,运行另一个线程从表尾插入数据;
  • 值为 ALWAYS (or 2)表示不管是否有删除的行,都允许在表尾插入数据。
show variable like '%concurrent_insert';

 

锁调度

  MyISAM 存储引擎中,假如同时一个读请求,一个写请求过来的话,会优先处理写请求,因为 MyISAM 存储引擎中认为写请求比读请求重要。这样就会导致,假如大量的读写请求过来,就会导致读请求长时间的等待,或者"线程饿死",因此 MyISAM 不适合运用于大量读写操作的场景,这样会导致长时间读取不到用户数据,用户体验感极差。

  可以通过设置low-priority-updates参数,设置请求链接的优先级,使得 MySQL 优先处理读请求。

 

InnoDB

  InnoDB 和 MyISAM 不同的是,InnoDB 支持行锁事务

  InnoDB 中除了有表锁行级锁的概念,还有 Gap Lock(间隙锁)、Next-key Lock锁,间隙锁主要用于范围查询的时候,锁住查询的范围,并且间隙锁也是解决幻读的方案

  InnoDB 中的行级锁是对索引加的锁,在不通过索引查询数据的时候,InnoDB 就会使用表锁。但是通过索引查询的时候是否使用索引,还要看 MySQL 的执行计划,MySQL 的优化器会判断是一条 sql 执行的最佳策略。若是 MySQL 觉得执行索引查询还不如全表扫描速度快,那么 MySQL 就会使用全表扫描来查询,这是即使 sql 语句中使用了索引,最后还是执行为全表扫描,加的是表锁。

 

InnoDB 的行锁与表锁

  InnoDB的行锁也是分为行级共享读锁(S锁)排它写锁(X锁),原理特点和 MyISAM 的表级锁两种模式是一样的。

  1. 执行非索引条件查询执行的是表锁。
  2. 执行索引查询是否是加行锁,还得看 MySQL 的执行计划,可以通过 explain 关键字来查看。
  3. 用普通键索引的查询,遇到索引值相同的,也会对其他的操作数据行的产生影响。

InnoDB 的间隙锁

  当使用范围条件查询而不是等值条件查询的时候,InnoDB 就会给符合条件的范围索引加锁,在条件范围内并不存的记录就叫做"间隙(GAP)"

  在事务的四大隔离级别中,不可重复读会产生幻读的现象,只能通过提高隔离级别到串行化来解决幻读现象。但是 MySQL 中的不可重复是已经解决了幻读问题,它通过引入间隙锁的实现来解决幻读,通过给符合条件的间隙加锁,防止再次查询的时候出现新数据产生幻读的问题。

  1. 主键索引不需要间隙锁——主键索引具有唯一性,不允许出现重复,那么当进行等值查询的时候id=3,只能有且只有一条数据,是不可能再出现id=3的第二条数据。因此它只要锁定这条数据(锁定索引),在下次查询当前读的时候不会被删除、或者更新id=3的数据行,也就保证了数据的一致性,所以主键索引由于他的唯一性的原因,是不需要加间隙锁的。
  2. 范围查询会加上间隙锁
  3. 是用不存在的检索条件,会使用间隙锁

死锁

  死锁在 InnoDB 中才会出现死锁,MyISAM 是不会出现死锁,因为 MyISAM支持的是表锁,一次性获取了所有的锁,其它的线程只能排队等候。而 InnoDB 默认支持行锁,获取锁是分步的,并不是一次性获取所有的锁,因此在锁竞争的时候就会出现死锁的情况。虽然 InnoDB 会出现死锁,但是并不影响 InnoDB 成为最受欢迎的存储引擎,MyISAM 可以理解为串行化操作,读写有序,因此支持的并发性能低下。

当一个事务开始并且update一条id=1的数据行时,成功获取到写锁,此时另一个事务执行也update另一条id=2的数据行时,也成功获取到写锁(id为主键)。
此时cpu将时间分配给了事务一,事务一接着也是update id=2的数据行,因为事务二已经获取到id=2数据行的锁,所以事务已处于等待状态。
事务二有获取到了时间,像执行update id=1的数据行,但是此时id=1的锁被事务一获取到了,事务二也处于等待的状态,因此形成了死锁。
死锁案例

 

死锁的解决方案

  要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。也可以设置参数innodb_lock_wait_timeout,超时时间,并且将参数innodb_deadlock_detect 打开,当发现死锁的时候,自动回滚其中的某一个事务。

      

总结

  MyISAM的表锁分为两种模式:「共享读锁」「排它写锁」。获取的读锁的线程对该数据行只能读,不能修改,其它线程也只能对该数据行加读锁。获取到写锁的线程对该数据行既能读也能写,对其他线程对该数据行的读写具有排它性。

  MyISAM中默认写优先于去操作,因此MyISAM一般不适合运用于大量读写操作的程序中。

  InnoDB的行锁虽然会出现死锁的可能,但是InnoDB的支持的并发性能比MyISAM好,行锁的粒度最小,一定的方法和措施可以解决死锁的发生,极大的发挥InnoDB的性能。

  InnoDB中引入了间隙锁的概念来决解出现幻读的问题,也引入事务的特性,通过事务的四种隔离级别,来降低锁冲突,提高并发性能。

 

 

 

                                         

posted @ 2020-07-20 17:14  ''竹先森゜  阅读(1535)  评论(0编辑  收藏  举报