数据库事务和锁

一、前言

此次咱们只谈数据库。以往遇到并发事务问题,总想着站在应用层面考虑问题,该如何实现悲观锁和乐观锁?但并不很清楚数据库中底层的实现原理。找几个晚上从新温习了一下大学课堂上的《数据库系统概览》,借这篇文章记下来此次读书的概要。mysql

二、四个特性 ACID

若是想要说明一个数据库或者一个框架支持事务性操做,则必需要知足下面的四大特性。sql

  1. 原子性(Atomicity):原子性是指事务是一个不可再分割的工做单元,事务中的操做要么都发生,要么都不发生。
  2. 一致性(Consistency):一致性是指在事务开始以前和事务结束之后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
  3. 隔离性(Isolation):多个事务并发访问时,事务之间是隔离的,一个事务不该该影响其它事务运行效果。
  4. 持久性(Durability):持久性是指事务的操做,一旦提交,对于数据库中数据的改变是永久性的,即便数据库发生故障也不能丢失已提交事务所完成的改变。

三、四种隔离级别

在数据库事务的ACID四个属性中,隔离性是一个最常放松的一个。能够在数据操做过程当中利用数据库的锁机制或者多版本并发控制机制获取更高的隔离等级。可是,随着数据库隔离级别的提升,数据的并发能力也会有所降低。因此,如何在并发性和隔离性之间作一个很好的权衡就成了一个相当重要的问题。数据库

3.一、并发问题

实际开发过程当中,咱们绝大部分的事务都是有并发状况。当多个事务并发运行,常常会操做相同的数据来完成各自的任务。在这种状况下可能会致使如下的问题:并发

  1. 脏读:指一个事务A正在访问数据,而且对该数据进行了修改,可是这种修改尚未提交到数据库中(也可能由于某些缘由Rollback了)。这时候另一个事务B也访问这个数据,而后使用了这个被A修改的数据,那么这个数据就是脏的,并非数据库中真实的数据。这就被称做脏读。
  2. 不可重复读: 事务 A 屡次读取同一数据,事务 B 在事务A屡次读取的过程当中,对数据做了更新并提交,致使事务A屡次读取同一数据时,结果不一致。
  3. 幻读:指一个事务A对一个表中的数据进行了修改,并且该修改涉及到表中全部的数据行;同时另外一个事务B也在修改表中的数据,该修改是向表中插入一行新数据。那么通过这一番操做以后,操做事务A的用户就会发现表中还有没修改的数据行,就像发生了幻觉同样。

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。oracle

3.二、隔离级别

ANSI/ISO SQL定义的标准隔离级别有四种,从低到高依次为:未提交读(Read uncommitted) < 提交读(Read committed) < 可重复读(Repeatable reads) < 序列化(Serializable)。框架

  1. 未提交读(Read Uncommitted):一个事务在执行过程当中,既能够访问其余事务未提交的新插入的数据,又能够访问未提交的修改数据。若是一个事务已经开始写数据,则另一个事务不容许同时进行写操做,但容许其余事务读此行数据。此隔离级别可防止丢失更新。
  2. 已提交读(Read Committed):一个事务在执行过程当中,既能够访问其余事务成功提交的新插入的数据,又能够访问成功修改的数据。读取数据的事务容许其余事务继续访问该行数据,可是未提交的写事务将会禁止其余事务访问该行。此隔离级别可有效防止脏读。
  3. 可重复读(Repeatable Read):一个事务在执行过程当中,能够访问其余事务成功提交的新插入的数据,但不能够访问成功修改的数据。读取数据的事务将会禁止写事务(但容许读事务),写事务则禁止任何其余事务。此隔离级别可有效防止不可重复读和脏读。
  4. 序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。但这个级别可能致使大量的超时现象和锁竞争,在实际应用中不多使用。

mysql:默认隔离级别是 Repeatable Read,会出现幻读的问题。oracle/sql server:默认隔离级别是 Read Committed,会出现不可重复读和幻读的问题。spa

3.三、间隙锁

产生幻读的缘由是,行锁只能锁住行,可是新插入记录这个动做,要更新的是记录之间的“间隙”。所以,为了解决幻读问题,mysql InnoDB 只好引入新的锁,也就是间隙锁 (GapLock)。间隙锁,锁的就是两个值之间的空隙。值得注意的是,间隙锁只在隔离级别是Repeatable read 下才会生效。线程

  1. 行锁(Record Lock):锁直接加在索引记录上面。
  2. 间隙锁(Gap Lock):锁加在不存在的空闲空间,能够是两个索引记录之间,也多是第一个索引记录以前或者最后一个索引以后的空间。
  3. Next-Key Lock:行锁与间隙锁组合起来用就叫作Next-Key Lock。

默认状况下,InnoDB工做在"可重复读"的隔离状况下,而且以Next-Key Lock的方式对数据进行加锁,这样就能够有效地防止"幻读"的发生。Next-key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。若是一个间隙被事务T1加了锁,其余事务是不能在这个间隙插入记录的。例以下面的sql,会对id取值范围大于100的间隙加锁。当增删数据库时若是id大于100则会阻塞住,若是小于100则不会有影响。code

select * from  emp where id > 100 for update;

四、锁

锁是数据库系统区别于文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问。粗略的分有行级锁、表级锁和页级锁,行级锁再分共享锁和排他锁等。要记住悲观锁和乐观锁是两个抽象的概念,并不是实际数据库中的锁。server

image.png

4.一、共享锁

共享锁(S锁):容许一个事务去读一行,会阻止其余事务获取相同数据集的排他锁(读取数据的时候不容许修改),也被称为读锁。当没有其余线程对查询结果集中的任何一行使用排他锁时,能够成功申请共享锁,不然会被阻塞。其余线程也能够读取使用了共享锁的表,并且这些线程读取的是同一个版本的数据。

mysql的共享锁支持行级锁,在查询语句后面增长LOCK IN SHARE MODE,mysql会对查询结果中的每行都加共享锁。

SELECT ... LOCK IN SHARE MODE;

oracle的共享锁不支持行级锁,只能是表级锁。

LOCK TABLE <表名>[,<表名>]... IN SHARE MODE [NOWAIT]

有一些误区须要记住,单纯的查询SELECT不是共享锁,其实是没有上任何锁。而 SELECT ... FOR UPDATE 是排他锁。

4.二、排他锁

排他锁(X锁):容许获取排他锁去作更新操做,阻止其余事务获取相同数据的共享锁和排他锁(一个事务修改数据的时候,阻止其余事务对相同数据集作更新或者查询操做),也被称为写锁。

排他锁的使用方式比较一致,新增、更新和删除等DML操做语句会自动加上排他锁。并且SELECT ... FOR UPDATE 也是排他锁。

4.三、意向锁

为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:

意向共享锁(IS):事务打算给数据行加共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加排他锁,事务再给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁是数据库隐式帮咱们作了,咱们不须要操心。

4.四、悲观锁和乐观锁(抽象)

悲观锁:是从数据库层面加锁。老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它释放锁。

乐观锁:老是假设最好的状况,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

那么在数据库层面上,咱们利用锁来实现事务的,不管是共享锁仍是排他锁,都是悲观锁。而乐观锁是经过业务来实现,并不会利用到数据库的锁。

相关文章
相关标签/搜索