Exploring
首页
  • Java

    • 面向对象的思想OOP
    • 浅谈Java反射原理
    • endorsed覆盖JDK中的类
  • 认证与授权

    • LDAP概念和原理介绍
    • OAuth2介绍
  • Impala

    • Impala 介绍
  • MySQL

    • 关于MySQL的一些面试题
    • 解决MySQL不到中文数据
    • 数据库之事务与实现原理
  • Oracle

    • oracle的表空间,用户管理,表操作,函数
    • oracle的查询、视图、索引
    • plsql简单入门
  • Redis

    • 数据类型详解
    • 跳越表
    • 数据持久化的两种方式
  • 共识算法

    • gossip
  • RPC

    • GRPC初识与快速入门
    • ProtocolBuffer基本语法
  • RabbitMQ

    • RabbitMQ入门程序之HelloWorld
    • RabbitMQ之工作模式
  • Zookeeper

    • Zookeeper一文入门
  • Docker

    • Docker入门初体验
  • Maven

    • 把自己的包到Maven中央仓库
    • Maven之自定义插件
  • Nginx

    • nginx的安装
    • nginx的配置文件
    • nignx 的变量
  • Tomcat

    • Servlet3通过SPI进行注册组件
  • Vagrant

    • vagrant 初始化
    • vagrant 常用配置
    • vagrant 自己制作 box
  • Linux

    • 启动方式 Systemd
    • 后台服务
    • 防火墙与 Iptables
  • 设计模式

    • 设计模式-代理
    • 设计模式-单例模式
    • 设计模式-迭代器
  • 分布式

    • CAP 理论
  • 数据结构

    • 数据结构之堆Heap
    • 数据结构之哈希表
    • 数据结构之队列
  • 计算机网络

    • HTTP与HTTPS详解
    • 浅谈DNS协议
    • ISP中的网络层
  • 算法

    • 常用查找算法及Java实现
    • 常用排序算法及Java实现
    • 迪杰斯特拉算法
  • 操作系统

    • 操作系统之进程调度算法
    • 操作系统之进程通讯IPC
    • 操作系统之内存管理
  • 抓包

    • 生成安卓系统证书
  • 加解密

    • 常见加密算法
    • 公开秘钥基础知识
    • RSA 解析
  • Windows

    • scoop 包管理
    • windows-terminal 配置
    • 增强 PowerShell
归档
Github (opens new window)
首页
  • Java

    • 面向对象的思想OOP
    • 浅谈Java反射原理
    • endorsed覆盖JDK中的类
  • 认证与授权

    • LDAP概念和原理介绍
    • OAuth2介绍
  • Impala

    • Impala 介绍
  • MySQL

    • 关于MySQL的一些面试题
    • 解决MySQL不到中文数据
    • 数据库之事务与实现原理
  • Oracle

    • oracle的表空间,用户管理,表操作,函数
    • oracle的查询、视图、索引
    • plsql简单入门
  • Redis

    • 数据类型详解
    • 跳越表
    • 数据持久化的两种方式
  • 共识算法

    • gossip
  • RPC

    • GRPC初识与快速入门
    • ProtocolBuffer基本语法
  • RabbitMQ

    • RabbitMQ入门程序之HelloWorld
    • RabbitMQ之工作模式
  • Zookeeper

    • Zookeeper一文入门
  • Docker

    • Docker入门初体验
  • Maven

    • 把自己的包到Maven中央仓库
    • Maven之自定义插件
  • Nginx

    • nginx的安装
    • nginx的配置文件
    • nignx 的变量
  • Tomcat

    • Servlet3通过SPI进行注册组件
  • Vagrant

    • vagrant 初始化
    • vagrant 常用配置
    • vagrant 自己制作 box
  • Linux

    • 启动方式 Systemd
    • 后台服务
    • 防火墙与 Iptables
  • 设计模式

    • 设计模式-代理
    • 设计模式-单例模式
    • 设计模式-迭代器
  • 分布式

    • CAP 理论
  • 数据结构

    • 数据结构之堆Heap
    • 数据结构之哈希表
    • 数据结构之队列
  • 计算机网络

    • HTTP与HTTPS详解
    • 浅谈DNS协议
    • ISP中的网络层
  • 算法

    • 常用查找算法及Java实现
    • 常用排序算法及Java实现
    • 迪杰斯特拉算法
  • 操作系统

    • 操作系统之进程调度算法
    • 操作系统之进程通讯IPC
    • 操作系统之内存管理
  • 抓包

    • 生成安卓系统证书
  • 加解密

    • 常见加密算法
    • 公开秘钥基础知识
    • RSA 解析
  • Windows

    • scoop 包管理
    • windows-terminal 配置
    • 增强 PowerShell
归档
Github (opens new window)
  • MySQL

    • 关于MySQL的一些面试题
    • 解决MySQL不到中文数据
    • 数据库之事务与实现原理
      • 为什么需要事务
      • 什么是数据库事务
      • ACID
        • 原子性(Atomicity)
        • 一致性(Consistency)
        • 隔离性(Isolation)
        • 持久性(Durability)
        • 事务中的ACID
      • 并发一致性问题
        • 丢失更新
        • 脏读
        • 脏写
        • 不可重读读
        • 幻读
      • 事务的隔离级别
        • 读未提交(READ UNCOMMITTED)
        • 读已提交(READ COMMITTED)
        • 可重复读(REPEATABLE READ)
        • 串行化(SERIALIZABLE)
        • MySQL中的事务隔离级别的操作
      • 事务隔离级别的实现-并发控制技术
        • 基于封锁的并发控制
        • 基于时间戳的并发控制
        • 基于有效性检查的并发控制
        • 基于多版本并发控制(MVCC)与快照隔离
      • 参考
    • MyISAM和InnoDB引擎区别
    • MySQL不同隔离级别下的加锁情况
    • MySql常用指令集
    • MySQL的索引
    • MySQL的字符集与校对规则
    • MySQL调优利器Explain
    • MySQL死锁的调试
    • MySQL之Gap-Locks与Next-key-Locks
    • SQL之组内排序
  • Oracle

  • Redis

  • 数据库
  • MySQL
unclezs
2020-08-09
0
目录

数据库之事务与实现原理

# 为什么需要事务

典型的就是转账问题了,转账是生活中常见的操作,比如从A账户转账100元到B账号。站在用户角度而言,这是一个逻辑上的单一操作,然而在数据库系统中,至少会分成两个步骤来完成:

  • 将A账户的金额减少100元
  • 将B账户的金额增加100元。

这个时候可能会出现问题:

  1. 转账操作的第一步执行成功,A账户上的钱减少了100元,但是第二步执行失败或者未执行便发生系统崩溃,导致B账户并没有相应增加100元。
  2. 转账操作刚完成就发生系统崩溃,系统重启恢复时丢失了崩溃前的转账记录。
  3. 同时又另一个用户转账给B账户,由于同时对B账户进行操作,导致B账户金额出现异常。

# 什么是数据库事务

事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。

BEGIN TRANSACTION  //事务开始
SQL1
SQL2
COMMIT/ROLLBACK   //事务提交或回滚
1
2
3
4

# ACID

# 原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。 回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

# 一致性(Consistency)

数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。

# 隔离性(Isolation)

一个事务所做的修改在最终提交以前,对其它事务是不可见的。并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。

# 持久性(Durability)

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。使用重做日志来保证持久性。

# 事务中的ACID

事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:

  • 只有满足一致性,事务的执行结果才是正确的。
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
  • 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
  • 事务满足持久化是为了能应对数据库崩溃的情况。

# 并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。

# 丢失更新

丢失更新是指事务覆盖了其他事务对数据的已提交修改,导致这些修改好像丢失了一样。

T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。

# 脏读

脏读是指一个事务读取了另一个事务未提交的数据,在事务1对A的处理过程中,事务2读取了A的值,但之后事务1回滚,导致事务2读取的A是未提交的脏数据。

# 脏写

脏写是指事务回滚了其他事务对数据项的已提交修改,比如下面这种情况,在事务1对数据A的回滚,导致事务2对A的已提交修改也被回滚了。

# 不可重读读

T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

# 幻读

T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。

产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 事务的隔离级别越低,可能出现的并发异常越多,但是通常而言系统能提供的并发能力越强。

# 事务的隔离级别

事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。 然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低。

SQL标准为事务定义了不同的隔离级别,从低到高依次是:

  1. 读未提交(READ UNCOMMITTED)
  2. 读已提交(READ COMMITTED)
  3. 可重复读(REPEATABLE READ)
  4. 串行化(SERIALIZABLE)

# 读未提交(READ UNCOMMITTED)

事务中的修改,即使没有提交,对其它事务也是可见的。

# 读已提交(READ COMMITTED)

一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。

# 可重复读(REPEATABLE READ)

保证在同一个事务中多次读取同样数据的结果是一样的。

# 串行化(SERIALIZABLE)

强制事务串行执行。需要加锁实现,而其它隔离级别通常不需要。

# MySQL中的事务隔离级别的操作

#查询隔离级别
SELECT @@transaction_isolation
SHOW variables like '%transaction_isolation%';
#设置隔离级别
SET transaction_isolation='READ-UNCOMMITTED';#读未提交
SET transaction_isolation='READ-COMMITTED';#读已提交
SET transaction_isolation='REPEATABLE-READ';#可重复读
SET transaction_isolation='SERIALIZABLE';#串行化
1
2
3
4
5
6
7
8

# 事务隔离级别的实现-并发控制技术

并发控制技术是实现事务隔离性的关键,实现方式有多种,并发控制策略可以分为两类:

  • 乐观并发控制:对于并发执行可能冲突的操作,假定其不会真的冲突,允许并发执行,直到真正发生冲突时才去解决冲突,比如让事务回滚。
  • 悲观并发控制:对于并发执行可能冲突的操作,假定其必定发生冲突,通过让事务等待(锁)或者中止(时间戳排序)的方式使并行的操作串行执行。

# 基于封锁的并发控制

# 封锁粒度

MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。 但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。 在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。

# 锁的种类

1.读写锁

  • 排它锁(Exclusive),简写为 X 锁,又称写锁。加了X锁,其他事务什么锁都不能加。
  • 共享锁(Shared),简写为 S 锁,又称读锁。加了S锁其他事务可以加S锁,不能加X锁。

2.意向锁(Intention Locks)

使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:

  • 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
  • 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。

通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。

各种锁的兼容关系如下:

- X IX S IS
X × × × ×
IX × √ × √
S × √ √ √
IS × √ √ √

解释如下:

  • 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁;
  • S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。

# 三级锁与两段锁协议

# 三级封锁协议

三级封锁协议就是对锁使用的规定,来解决事务并发一致性问题。

a.一级封锁-解决丢失更新

事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。 可以解决丢失更新问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。

b.二级封锁-解决脏读

在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。

可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。

c.三级封锁-解决不可重复读

在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。

可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。

# 两段锁协议

加锁和解锁分为两个阶段进行。

可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。

事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。

lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
1

但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。

lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
1

# MySQL隐式与显示锁定

MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。

InnoDB 也可以使用特定的语句进行显示锁定:

SELECT ... LOCK In SHARE MODE; #S锁
SELECT ... FOR UPDATE; #X锁
1
2

# 基于时间戳的并发控制

核心思想:对于并发可能冲突的操作,基于时间戳排序规则选定某事务继续执行,其他事务回滚。

系统会在每个事务开始时赋予其一个时间戳,这个时间戳可以是系统时钟也可以是一个不断累加的计数器值,当事务回滚时会为其赋予一个新的时间戳,先开始的事务时间戳小于后开始事务的时间戳。

每一个数据项Q有两个时间戳相关的字段: W-timestamp(Q):成功执行write(Q)的所有事务的最大时间戳 R-timestamp(Q):成功执行read(Q)的所有事务的最大时间戳

具体排序方式就是:

  1. 假设事务T发出read(Q),T的时间戳为TS a. 若TS(T)<W-timestamp(Q),则T需要读入的Q已被覆盖。此 read操作将被拒绝,T回滚。 b. 若TS(T)>=W-timestamp(Q),则执行read操作,同时把 R-timestamp(Q)设置为TS(T)与R-timestamp(Q)中的最大值
  2. 假设事务T发出write(Q) a.若TS(T)<R-timestamp(Q),write操作被拒绝,T回滚。 b.若TS(T)<W-timestamp(Q),则write操作被拒绝,T回滚。 c.其他情况:系统执行write操作,将W-timestamp(Q)设置 为TS(T)。

基于时间戳排序和基于锁实现的本质一样:对于可能冲突的并发操作,以串行的方式取代并发执行,因而它也是一种悲观并发控制。它们的区别主要有两点:

  • 基于锁是让冲突的事务进行等待,而基于时间戳排序是让冲突的事务回滚。
  • 基于锁冲突事务的执行次序是根据它们申请锁的顺序,先申请的先执行;而基于时间戳排序是根据特定的时间戳排序规则。

# 基于有效性检查的并发控制

核心思想:事务对数据的更新首先在自己的工作空间进行,等到要写回数据库时才进行有效性检查,对不符合要求的事务进行回滚。

基于有效性检查的事务执行过程会被分为三个阶段:

  1. 读阶段: 数据项被读入并保存在事务的局部变量中。所有write操作都是对局部变量进行,并不对数据库进行真正的更新。
  2. 有效性检查阶段: 对事务进行有效性检查,判断是否可以执行write操作而不违反可串行性。如果失败,则回滚该事务。
  3. 写阶段: 事务已通过有效性检查,则将临时变量中的结果更新到数据库中。

有效性检查通常也是通过对事务的时间戳进行比较完成的,不过和基于时间戳排序的规则不一样。

该方法允许可能冲突的操作并发执行,因为每个事务操作的都是自己工作空间的局部变量,直到有效性检查阶段发现了冲突才回滚。因而这是一种乐观的并发策略。

# 基于多版本并发控制(MVCC)与快照隔离

# 什么是MVCC

多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

可以认为MVCC是行级锁的一个变种,但是在很多情况下又避免了加锁,所以效率比较高。

MySQL的InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列实现:

  • 创建版本号:指示创建一个数据行的快照时的系统版本号;
  • 删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。

其中系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。事务版本号:事务开始时的系统版本号。

MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。

# 实现过程

以下实现过程针对可重复读隔离级别。

当开始一个事务时,该事务的版本号肯定大于当前所有数据行快照的创建版本号,理解这一点很关键。数据行快照的创建版本号是创建数据行快照时的系统版本号,系统版本号随着创建事务而递增,因此新创建一个事务时,这个事务的系统版本号比之前的系统版本号都大,也就是比所有数据行快照的创建版本号都大。

1.SELECT

①只查找版本早于当前事务版本的数据行(行的系统版本号小于等于事务的系统版本号),这样可以保证要么数据行是之前存在的,要么就是自己这个事务自己修改的。

②查找行的删除版本号要么大于当前事务版本号,要么未定义。这样可以保证这个数据行没有被删除的。

2.INSERT

将当前系统版本号作为数据行快照的创建版本号。

3.DELETE

将当前系统版本号作为数据行快照的删除版本号。

4.UPDATE

将当前系统版本号作为更新前的数据行快照的删除版本号,并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。

# 快照读与当前读

1.快照读

使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。

select * from table ...;
1

2.当前读 读取的是最新的数据,不去读快照,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。

select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;
1
2
3
4
5

# 参考

  1. 数据库系统原理 (opens new window)
  2. 数据库事务的概念及其实现原理 (opens new window)
  3. MVCC多版本并发控制 (opens new window)
在 GitHub 编辑此页 (opens new window)
上次更新: 2024/02/25, 12:11:11
解决MySQL不到中文数据
MyISAM和InnoDB引擎区别

← 解决MySQL不到中文数据 MyISAM和InnoDB引擎区别→

Theme by Vdoing | Copyright © 2018-2024 unclezs
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式