MySQL数据库(十)——锁

(一)锁

当数据库有并发事务的时候,可能会产生数据不一致,这时候需要一些机制来保证访问次序,锁机制就是这样的一个机制。

1)隔离级别与锁的关系

  1. 在ReadUncommitted级别下,读取数据不需要加共享锁,这样就不会和被修改的数据上的排他锁冲突。
  2. 在ReadCommitted级别下,读操作需要加共享锁,但是在语句执行完之后释放共享锁。
  3. 在RepeatableRead级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,即在事务执行完毕以后才释放共享锁。
  4. Serializable是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。

2)数据库锁粒度

在关系型数据库中可以按照锁的粒度把数据库锁分为行级锁(InnoDB引擎)、表级锁(MyISAM引擎)和页级锁(BDB引擎)。

(1)行级锁

行级锁是MySQL中锁定粒度最细的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突,加锁粒度最小,单加锁的开销也最大。行级锁分为共享锁和排他锁。

特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。

(2)表级锁

表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁。实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MyISAM和InnoDB都支持表级锁定。表级锁分为表共享锁(共享锁)和表独占写锁(排他锁)。

特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度低。

(3)页级锁

页级锁是MySQL中锁定粒度介于行锁和表锁中间的一种锁。表锁速度快但冲突多,行锁冲突少但速度慢,所以取了折中的页级,一次锁定相邻的一组记录。

特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。

(4)对比

表锁:开销小、加锁快、不会出现死锁、锁粒度较大、发生锁冲突的概率比较大。

行锁:开销大、加锁慢、会出现死锁、锁粒度较小、发生锁冲突的概率较小。

页锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。

3)数据库锁类别

从锁的类别上来讲,有共享锁和排他锁。

(1)共享锁

共享锁,又叫做读锁。当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。

(2)排他锁

排他锁就叫做写锁。当用户要进行数据的写入时对数据加上排他锁,排他锁只可以加一个,和其他的共享锁、排他锁都相斥。

(二)引擎的锁

1)MyISAM表锁

  • MyISAM支持表锁,不支持事务处理、不支持外键。
  • MyISAM并发比较简单,只支持表锁粒度,锁的粒度较大。但是不会引起死锁,它支持表共享地读锁和表互斥地写锁。
  • MyISAM表的读操作:不会阻塞其他用户对同一张表的读操作,但是阻塞其他用户对同一张表的写操作。
  • MyISAM表的写操作:会阻塞其他用户对同一个表的读、写操作。
  • MyISAM的读写互斥、写写互斥,读读共享。
  • 粒度:可控制范围(表、行)

2)InnoDB行锁

  • InnoDB支持事务,支持外键,支持行级锁,并发程度高。
  • InnoDB支持两种类型的行锁:
  • 1)共享锁(S):允许一个事务去读一行,组织其他事务获取相同的数据集的排他锁。
  • 2)排他锁(X):允许获得其他锁的事务更新数据,组织其他事务获取悬停数据集的共享锁和排他锁。
  • InnoDB中行锁是通过给索引上的索引选项加锁实现的,而不是给表中的行记录进行加锁。意味着如果表中的行不存在索引,InnoDB使用表锁来实现。

InnoDB是基于索引来完成行锁

例如select * from tab_with_index where id = 1 for update;

for tpdate可以根据条件来完成行锁锁定,并且id是有索引键的列,如果id不是索引键,那么InnoDB将完成表锁。

  • InnoDB会产生死锁,例如:对表tb1、tb2进行数据查询,有两个窗口进行操作,设置为手动提交事务。
  • 窗口1:先查询tb1,再查询tb2,然后进行事务提交commit。
  • 窗口2:先查询tb2,再查询tb1,然后进行事务提交commit。

3)InnoDB存储索引的锁的算法

  1. Record lock:单个行记录上的锁
  2. Gap lock:间隙锁,锁定一个范围,不包括记录本身。
  3. Next-key lock:record+gap锁定一个范围,包含记录本身。

4)相关补充

  1. InnoDB对于行的查询使用Next-key lock。
  2. Next-locking keying为了解决Phantom Problem幻读问题。
  3. 当查询的索引含有唯一属性时,将next-key lock降级为record key。
  4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,这会导致幻读问题的产生。
  5. 有两种方式显式关闭gap锁:除了外键约束和唯一性检查外,其余情况仅使用record lock

A、将事务隔离级别设置为RC。

B、将参数innodb_locks_unsafe_for_binlog设置为1。

(三)数据库死锁

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

1)常见的解决方法

  1. 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  2. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
  3. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定粒度,通过表级锁来减少死锁产生的概率。
  4. 如果业务处理不好,可以使用分布式事务锁或者乐观锁。

2)乐观锁悲观锁

数据库管理系统(DBMS)中的并发控制的任务是,确保在多个事务同时存取数据库中同一数据时,不破坏事务的隔离性和统一性以及数据库的统一性。乐观锁和悲观锁是两种主要的技术手段。

(1)介绍

  • 悲观锁:假设一定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。

实现方式:使用数据库中的锁机制。

  • 乐观锁假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。

实现方式:一般使用版本号控制或者CAS算法实现。

(2)使用场景

乐观锁适用于写比较少的情况下(多读场景)。即冲突真的很少发生的时候,这样可以省去锁的开销,加大系统的吞吐量。

但如果是多写的情况,一般会经常产生冲突,这样会导致上层应用会不断的进行retry,这样反而降低性能,所以在多写的场景下使用悲观锁比较合适。

你可能感兴趣的:(数据库MySQL)