数据库
首页 > 数据库> > 20200129 4. MySQL 事务和锁 - 拉勾教育

20200129 4. MySQL 事务和锁 - 拉勾教育

作者:互联网

MySQL 事务和锁

ACID 特性

原子性

原子性:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

修改---》Buffer Pool修改---》刷盘。可能会有下面两种情况:

每一个写事务,都会修改 Buffer Pool,从而产生相应的 Redo/Undo 日志,在 Buffer Pool 中的页被刷到磁盘之前,这些日志信息都会先写入到日志文件中,如果 Buffer Pool 中的脏页没有刷成功,此时数据库挂了,那在数据库再次启动之后,可以通过 Redo 日志将其恢复出来,以保证脏页写的数据不会丢失。如果脏页刷新成功,此时数据库挂了,就需要通过Undo来实现了。

持久性

持久性:指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,后续的操作或故障不应该对其有任何影响,不会丢失。

如下图所示,一个“提交”动作触发的操作有:Binlog 落地、发送 Binlog 、存储引擎提交、flush_logs,check_point、事务提交标记等。这些都是数据库保证其数据完整性、持久性的手段。

img

MySQL 的持久性也与 WAL 技术相关, redo log 在系统 Crash 重启之类的情况时,可以修复数据,从而保障事务的持久性。通过原子性可以保证逻辑上的持久性,通过存储引擎的数据刷盘可以保证物理上的持久性。

隔离性

隔离性:指的是一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他的并发事务是隔离的。

InnoDB 支持的隔离性有 4 种,隔离性从低到高分别为:读未提交、读提交、可重复读、可串行化。锁和多版本控制(MVCC)技术就是用于保障隔离性的。

一致性

一致性:指的是事务开始之前和事务结束之后,数据库的完整性限制未被破坏。一致性包括两方面的内容,分别是约束一致性和数据一致性。

一致性也可以理解为数据的完整性。数据的完整性是通过原子性、隔离性、持久性来保证的,而这 3 个特性又是通过 Redo/Undo 来保证的。逻辑上的一致性,包括唯一索引、外键约束、check 约束,这属于业务逻辑范畴。

img

ACID 及它们之间的关系如下图所示,4 个特性中有 3 个与 WAL 有关系,都需要通过 Redo、Undo 日志来保证等。

WAL 的全称为 Write-Ahead Logging,先写日志,再写磁盘。

img

事务控制的演进

并发事务

事务并发处理可能会带来一些问题,比如:更新丢失、脏读、不可重复读、幻读等。

排队

最简单的方法,就是完全顺序执行所有事务的数据库操作,不需要加锁,简单的说就是全局排队。序列化执行所有的事务单元,数据库某个时刻只处理一个事务操作,特点是强一致性,处理性能低。

排他锁

引入锁之后就可以支持并发处理事务,如果事务之间涉及到相同的数据项时,会使用排他锁,或叫 互斥锁,先进入的事务独占数据项以后,其他事务被阻塞,等待前面的事务释放锁。

img

注意,在整个事务 1 结束之前,锁是不会被释放的,所以,事务 2 必须等到事务 1 结束之后开始。

读写锁

读和写操作:读读、写写、读写、写读。

读写锁就是进一步细化锁的颗粒度,区分读操作和写操作,让读和读之间不加锁,这样下面的两个事务就可以同时被执行了。

img

读写锁,可以让读和读并行,而读和写、写和读、写和写这几种之间还是要加排他锁。

MVCC

多版本控制 MVCC,也就是 Copy on Write 的思想。MVCC 除了支持读和读并行,还支持读和写、写和读的并行,但为了保证一致性,写和写是无法并行的。

img

在事务 1 开始写操作的时候会 copy 一个记录的副本,其他事务读操作会读取这个记录副本,因此不会影
响其他事务对此记录的读取,实现写和读并行。

MVCC 概念

MVCC(Multi Version Concurrency Control)被称为多版本控制,是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过事务的可见性来保证事务能看到自己应该看到的数据版本。多版本控制很巧妙地将稀缺资源的独占互斥转换为并发,大大提高了数据库的吞吐量及读写性能。

如何生成的多版本?每次事务修改操作之前,都会在 Undo 日志中记录修改之前的数据状态和事务号,该备份记录可以用于其他事务的读取,也可以进行必要时的数据回滚。

MVCC 实现原理

MVCC 最大的好处是读不加锁,读写不冲突。在读多写少的系统应用中,读写不冲突是非常重要的,极大的提升系统的并发性能,这也是为什么现阶段几乎所有的关系型数据库都支持 MVCC 的原因,不过目前 MVCC 只在 Read Commited 和 Repeatable Read 两种隔离级别下工作。

在 MVCC 并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read):

举一个记录更新的案例来讲解 MVCC 中多版本的实现:

假设 F1~F6 是表中字段的名字,1~6 是其对应的数据。后面三个隐含字段分别对应该行的隐含 ID、事务号和回滚指针,如下图所示:

img

具体的更新过程如下:

假如一条数据是刚 INSERT 的,DB_ROW_ID 为 1,其他两个字段为空。当事务 1 更改该行的数据值时,会进行如下操作,如下图所示:

img

接下来事务2操作,过程与事务 1 相同,此时 Undo log 中会有两行记录,并且通过回滚指针连在一起,通过当前记录的回滚指针回溯到该行创建时的初始内容,如下图所示:

img

MVCC 已经实现了读读、读写、写读并发处理,如果想进一步解决写写冲突,可以采用下面两种方案:

事务隔离级别

隔离级别类型

前面提到的“更新丢失”、”脏读”、“不可重复读”和“幻读”等并发事务问题,其实都是数据库一致性问题,为了解决这些问题,MySQL 数据库是通过事务隔离级别来解决的,数据库系统提供了以下 4 种事务隔离级别供用户选择:

事务隔离级别 回滚覆盖 脏读 不可重复读 提交覆盖 幻读
读未提交 ×
读已提交 × ×
可重复读 × × × ×
串行化 × × × × ×

数据库的事务隔离级别越高,并发问题就越小,但是并发处理能力越差(代价)。读未提交隔离级别最低,并发问题多,但是并发处理能力好。以后使用时,可以根据系统特点来选择一个合适的隔离级别,比如对不可重复读和幻读并不敏感,更多关心数据库并发处理能力,此时可以使用 Read Commited 隔离级别。

事务隔离级别,针对 InnoDB 引擎,支持事务的功能。和 MyISAM 引擎没有关系。

事务隔离级别和锁的关系

  1. 事务隔离级别是 SQL92 定制的标准,相当于事务并发控制的整体解决方案,本质上是对锁和 MVCC 使用的封装,隐藏了底层细节。
  2. 锁是数据库实现并发控制的基础,事务隔离性是采用锁来实现,对相应操作加不同的锁,就可以防止其他事务同时对数据进行读写操作。
  3. 对用户来讲,首先选择使用隔离级别,当选用的隔离级别不能解决并发问题或需求时,才有必要在开发中手动的设置锁。

MySQL 默认隔离级别:可重复读

Oracle、SQL Server 默认隔离级别:读已提交

一般使用时,建议采用默认隔离级别,然后存在的一些并发问题,可以通过悲观锁、乐观锁等实现处理。

MySQL 隔离级别控制

MySQL 默认的事务隔离级别是 Repeatable Read,查看 MySQL 当前数据库的事务隔离级别命令如下:

show variables like 'tx_isolation';
select @@tx_isolation;

设置事务隔离级别可以如下命令:

set tx_isolation='READ-UNCOMMITTED';
set tx_isolation='READ-COMMITTED';
set tx_isolation='REPEATABLE-READ';
set tx_isolation='SERIALIZABLE';

锁机制和实战

锁分类

在 MySQL 中锁有很多不同的分类:

行锁原理

在 InnoDB 引擎中,我们可以使用行锁和表锁,其中行锁又分为共享锁和排他锁。 InnoDB 行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种: Record Lock 、 Gap Lock 和 Next-key Lock:

在 RR 隔离级别,InnoDB 对于记录加锁行为都是先采用 Next-Key Lock,但是当 SQL 操作含有唯一索引时,InnoDB 会对 Next-Key Lock 进行优化,降级为 Record Lock,仅锁住索引本身而非范围。

下面以 update t1 set name='XX' where id=10 操作为例,举例子分析下 InnoDB 对不同索引的加锁行
为,以 RR 隔离级别为例:

悲观锁

悲观锁(Pessimistic Locking),是指在数据处理过程,将数据处于锁定状态,一般使用数据库的锁机制实现。从广义上来讲,前面提到的行锁、表锁、读锁、写锁、共享锁、排他锁等,这些都属于悲观锁范畴。

乐观锁

乐观锁是相对于悲观锁而言的,它不是数据库提供的功能,需要开发者自己去实现。在数据库操作时,想法很乐观,认为这次的操作不会导致冲突,因此在数据库操作时并不做任何的特殊处理,即不加锁,而是在进行事务提交时再去判断是否有冲突了。

乐观锁实现的关键点:冲突的检测。

悲观锁和乐观锁都可以解决事务写写并发,在应用中可以根据并发处理能力选择区分,比如对并发率要求高的选择乐观锁;对于并发率要求低的可以选择悲观锁。

死锁与解决方案

下面介绍几种常见的死锁现象和解决方案:

产生原因 1:

如果在事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定(等价于表级锁),多个这样的事务执行后,就很容易产生死锁和阻塞,最终应用系统会越来越慢,发生阻塞或死锁。

解决方案 1:

SQL语句中不要使用太复杂的关联多表的查询;使用 explain 执行计划 对SQL语句进行分析,对于有全表扫描和全表锁定的SQL语句,建立相应的索引进行优化。

产生原因2:

两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁

解决方案2:

在同一个事务中,尽可能做到一次锁定所需要的所有资源按照 id 对资源排序,然后按顺序进行处理

标签:事务,加锁,隔离,记录,拉勾,并发,20200129,MySQL,操作
来源: https://www.cnblogs.com/huangwenjie/p/14346331.html