logo头像

小时候,哭是我们解决问题的绝招。长大后,笑是我们面对现实的武器。

MySQL中 乐观锁、悲观锁、共享锁、排它锁、行锁、表锁的理解

本文于 1149 天之前发表,文中内容可能已经过时。

MySQL

前言

MySQL/InnoDB的加锁,一直是一个面试中常见的话题。例如,数据库如果有高并发请求,如果保证数据完整性?产生死锁问题如何排查并解决?我在工作过程中,也会经常用到,乐观锁,排它锁 等。最近针对这几个概念进行学习,记录一下。

注: MySQL是一个支持插件式存储引擎的数据库系统。本文下面的所有介绍,都是基于InnoDB存储引擎,其他引擎的表现,会有较大的区别。

查看存储引擎

MySQL给开发者提供了查询存储引擎的功能,我这里使用的是MySQL5.6.4,可以使用:

1
SHOW ENGINES

engines

乐观锁

用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库增加一个数字类型的“version”字段来实现。当读数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次读取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

举例

  1. 数据库表设计

三个字段,分别是id、value、version

1
select id,value,version from TABLE where id=#{id}
  1. 每次更新表中的value字段时,为了防止发生冲突,需要这样操作
1
2
3
update TABLE
set value=2,version=version+1
where id=#{id} and version=#{version};

悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个概念就出来了,他们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同实现,它俩都属于悲观锁的范畴。

使用排它锁举例:

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作时,MySQL会立刻将结果进行提交。

我们可以使用命令设置MySQL为非autocommit模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
set autocommit=0;

# 设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

# 1. 开始事务

begin;/begin work;/start transaction; (三者选一就可以)

# 2. 查询表信息

select status from TABLE where id=1 for update;

# 3. 插入一条数据

insert into TABLE (id,value) values (2,2);

# 4. 修改数据为

update TABLE set value=2 where id=1;

# 5. 提交事务

commit;/commit work;

共享锁

共享锁又称读锁 read lock,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。

如果事务T对数据A加上共享锁后,则其他事务只能对数据A再加共享锁,不能加排他锁。获得共享锁的事务只能读数据,不能修改数据

排他锁

排他锁 exclusive lock(也叫writer lock)又称写锁

排它锁是悲观锁的一种实现,在上面悲观锁也介绍过。

若事务 1 对数据对象A加上X锁,事务 1 可以读A也可以修改A,其他事务不能再对A加任何锁,直到事物 1 释放A上的锁。这保证了其他事务在事物 1 释放A上的锁之前不能再读取和修改A。排它锁会阻塞所有的排它锁和共享锁

读取为什么要加读锁呢:防止数据在被读取的时候被别的线程加上写锁,

使用方式:在需要执行的语句后面加上for update就可以了

行锁

行锁又分共享锁排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

NOTE: 行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。

表锁

如何加表锁

innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.

Innodb中的行锁与表锁

前面提到过,在Innodb引擎中既支持行锁也支持表锁,那么什么时候会锁住整张表,什么时候或只锁住一行呢?
只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。行级锁的缺点是:由于需要请求大量的锁资源,所以速度慢,内存消耗大。

死锁

死锁(Deadlock)

所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。

下列方法有助于最大限度地降低死锁:

  1. 按同一顺序访问对象。
  2. 避免事务中的用户交互。
  3. 保持事务简短并在一个批处理中。
  4. 使用低隔离级别。
  5. 使用绑定连接。