【MySQL】事务管理

news2024/9/27 19:18:05

​🌠 作者:@阿亮joy.
🎆专栏:《零基础入门MySQL》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉CURD 不加控制会有什么问题👈
    • 👉什么是事务👈
    • 👉为什么会出现事务👈
    • 👉事务的版本支持👈
    • 👉事务提交方式👈
    • 👉事务常见操作方式👈
    • 👉事务隔离级别👈
      • 如何理解隔离性1
      • 隔离级别
      • 查看与设置隔离级别
      • 读未提交
      • 读提交
      • 可重复读
      • 串行化
      • 一致性
    • 👉如何理解隔离性2👈
      • 读写
      • RR 与 RC 的本质区别
    • 👉总结👈

👉CURD 不加控制会有什么问题👈

假如现在有一个业务需求,就是张三要给李四微信转账 50 块钱,那么如何解决这个业务需求呢?我想,我们可以采用下面的方案。

# 判断张三的微信余额是否多于 50 元
select money for table_name where name='张三' and money>=50

# 给张三微信余额减 50,李四微信余额加 50
update table_name set money=money-50 where name='张三';
update table_name set money=money+50 where name='李四';

那么上面的语句能不能全部都成功执行呢,会不会出现张三转账成功李四却没有收到钱的情况呢?很明显,如果不加以控制的话。是会出现这样的情况的。

那现实生活中是如何解决这样的问题的呢?其实就是将上面的 SQL 语句看做一个整体,要么不做,要么做完(绝对成功和绝对失败),不会出现一方转账另一方没收到转账的中间状态,这其实就是原子性。而这些 SQL 语句形成的整体就是一个事务,事务就是一个或者多个 SQL 语句的集合。

从上面的例子中,我们可以得到一下几个结论:

  • 上层看似比较简单的需求,对应的后端可能要做很多工作,组合起来才是一个完整的解决需求的方案。
  • 事务本身不是数据库与生俱来就有的,事务的本质工作就是为了简化程序员的工作模型。

👉什么是事务👈

事务就是一组 DML 语句组成,这些语句在逻辑上存在相关性,这一组 DML 语句要么全部成功,要么全部失败,是一个整体。MySQL 提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。

事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。假设一种场景:你毕业了,学校的教务系统后台 MySQL 中,不再需要你的数据,要删除你的所有信息,那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。

正如我们上面所说,一个 MySQL 数据库,可不止一个事务在运行,同一时刻甚至有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,也可能很多 SQL。这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至因为事务由多条 SQL 构成,那么也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?

所以一个完整的事务绝对不是简单的 SQL 集合,还需要满足如下四个属性:

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

上面四个属性,可以简称为 ACID。

  • 原子性(Atomicity,或称不可分割性)
  • 一致性(Consistency)
  • 隔离性(Isolation,又称独立性)
  • 持久性(Durability)
  • 其中原子性、隔离性和持久性是手段,一致性是目的。只要原子性、隔离性和持久性能够保证,那么一致性一定能够保证。

👉为什么会出现事务👈

事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了。服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。

备注:我们后面把 MySQL 中的一行信息,称为一行记录。

👉事务的版本支持👈

在 MySQL 中只有使用了 InnoDB 数据库引擎的数据库或表才支持事务, MyISAM 不支持。

查看数据库引擎

mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

👉事务提交方式👈

事务的提交方式常见的有两种:

  • 自动提交
  • 手动提交

查看事务提交方式

-- ON 表示自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

用 SET 来改变 MySQL 的自动提交模式

-- set autocommit=0 禁止自动提交
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

-- set autocommit=1 开启自动提交
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

👉事务常见操作方式👈

查看隔离级别

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ | -- 可重复读
+-----------------+
1 row in set, 1 warning (0.00 sec)

设置读未提交的隔离级别

mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

注:设置完隔离级别需要重新登录才能生效。

创建测试表

mysql> create table if not exists emp(
    -> id int primary key,
    -> name varchar(32) not null,
    -> sal decimal(10,2) not null default 0.0
    -> );
Query OK, 0 rows affected (0.02 sec)

正常演示:证明事务的开始与回滚

-- 查看事是否自动提交。我们故意设置成自动提交,看看该选项是否影响begin
mysql> show variables like 'autocommit'; 
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)

-- 开始一个事务begin也可以,推荐begin
mysql> start transaction; 
Query OK, 0 rows affected (0.00 sec)

mysql> insert into emp values(1, '张三', 1800);
Query OK, 1 row affected (0.00 sec)

在这里插入图片描述

mysql> savepoint s2;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into emp values(2, '李四', 2000);
Query OK, 1 row affected (0.00 sec)

在这里插入图片描述

-- 直接rollback,回滚在最开始,即使设置了很多保存点
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

-- commit 结束事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

在这里插入图片描述

重复上面的操作但是此时回滚到保存点 s2 和 s1

-- 李四的记录被删除
mysql> rollback to s2;
Query OK, 0 rows affected (0.00 sec)

-- 张三的记录被删除
mysql> rollback to s1;
Query OK, 0 rows affected (0.00 sec)

在这里插入图片描述
非正常演示 1:证明未 commit,客户端崩溃,MySQL 自动会回滚(隔离级别设置为读未提交)

在这里插入图片描述

非正常演示 2:证明 commit 了,客户端崩溃,MySQL 数据不会再受影响,已经持久化

在这里插入图片描述
非正常演示 3:对比试验,证明 begin 操作会自动更改提交方式,不会受 MySQL 是否自动提交影响

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过上面的对比实验,我们可以知道 autocommit 是否自动提交的设置并不会影响用户手动开启和提交事务。

非正常演示 4:证明单条 SQL 与事务的关系

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过上面的三个实验的对比,我们可以知道:我们可以知道单 SQL 默认都是以事务的方式进行提交的,只不过这个事务只有一条 SQL 语句。我们没有使用 begin 开启事务,我们写的 SQL 默认就是一个事务,SQL 执行完毕会按照 autocommit 来决定是否自动提交。

结论

  • 只要输入 begin 或者 start transaction,事务便必须要通过 commit 提交,才会持久化,与是否设置 set autocommit 无关。
  • 事务可以手动回滚,而当操作异常,MySQL 也会自动回滚。
  • InnoDB 的每一条 SQL 语句都默认封装成事务,自动提交。(select有特殊情况,因为MySQL 有 MVCC)。
  • 从上面的例子,我们能看到事务本身的原子性(rollback),持久性(commit)。

事务操作注意事项

  • 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)。
  • 如果一个事务被提交了(commit),则不可以回退(rollback)。
  • 可以选择回退到哪个保存点。
  • InnoDB 支持事务,MyISAM 不支持事务。
  • 开始事务可以使用 start transaction 或者 begin。
  • 事务中所谓的提交 commit 病史将数据进行刷盘,刷盘的过程是 mysqld 自己会执行的。commit 是设置事务的状态为已经完成,并将该数据交付给 mysqld。

👉事务隔离级别👈

如何理解隔离性1

  • MySQL 服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行。
  • 一个事务可能由多条 SQL 构成,也就意味着任何一个事务都有执行前、执行中和执行后的阶段。而所谓的原子性,其实就是让用户层要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务对用户表现出来的特性就是原子性。
  • 但毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个 SQL 的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
  • 数据库为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性。数据库允许事务受不同程度的干扰,就有了一种重要特征:隔离级别。
  • 隔离级别就是在依然保证数据安全的前提下,在隔离事务这件事情上所做的不同程度的妥协,妥协就是在效率和可靠性之间找出一个好的平衡点。隔离级别越高,效率越低。

隔离级别

  • 读未提交【Read Uncommitted】:在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。实际生产中不可能使用这种隔离级别的,相当于没有任何隔离性,会有很多并发问题,如脏读,幻读,不可重复读等。我们上面为了做实验方便,用的就是这个隔离性。
  • 读提交【Read Committed】:该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的),它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
  • 可重复读【Repeatable Read】:这是 MySQL 默认的隔离级别,它确保同一个事务在执行中多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
  • 串行化【Serializable】:这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争。这种隔离级别太极端,实际生产基本不使用。

隔离基本都是通过锁实现的,不同的隔离级别使用的锁是不同的。常见的有表锁,行锁,读锁,写锁,间隙(GAP),Next-Key锁(GAP+行锁)等。不过我们目前现有这个认识就行,先关注上层的使用。

查看与设置隔离级别

查看隔离级别

-- 查看隔离级别
mysql> select @@global.tx_isolation;  -- --查看全局隔级别
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@session.tx_isolation;  -- 查看当前会话的隔离级别(本次登录有效,重新登录会继承全局隔离级别)
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@tx_isolation;  -- 默认与当前会话的隔离级别相同
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

设置隔离级别

-- 设置当前会话或全局隔离级别
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
-- 设置当前会话的隔离级别(只会影响当前会话, 不会影响其他会话)
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE           |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@global.tx_isolation;  -- 全局隔离级别还是 RR
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

--设置全局隔离性,另起一个会话,会被影响
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED      |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

当 mysqld 重启后,mysql 的隔离级别都会变成默认的隔离级别可重复读。当设置全局隔离级别,重新登录后,另外两个隔离级别与全局隔离级别相同。如果没有重新登录,会话隔离级别还会是原来的隔离级别。

读未提交

读未提交这种隔离级别会造成一种现象,就是一个事务进行的操作还没有 commit,就被另一个事务看到了。这种读未提交的隔离级别几乎没有加锁,虽然效率高,但是问题太多,非常不建议使用。

-- 终端 A
-- 将隔离级别设置为读未提交
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

-- 重启客户端, 让隔离级别生效
mysql> quit;
Bye

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  1 | 张三   | 1800.00 |
|  2 | 李四   | 2000.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 更新数据
mysql> update emp set sal=10000 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 没有 commit 事务
-- 终端 B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 终端 B 读到了终端 A 还没有 commit 的数据(删除和增加也会这样)
mysql> select * from emp;
+----+--------+----------+
| id | name   | sal      |
+----+--------+----------+
|  1 | 张三   | 10000.00 |
|  2 | 李四   |  2000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

一个事务在执行中读到另一个执行中事务的更新或者其他操作,但是未 commit 的数据,这种现象叫做脏读(dirty read)。脏读会造成的问题:如果读取到的数据最终被回滚,那么事务可能基于错误的数据做出决策,导致数据不一致或错误的计算结果。

读提交

-- 终端 A
-- 设置隔离级别为读提交
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

-- 重启客户端,让隔离级别生效
mysql> quit;
Bye

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)

-- 终端 A
-- 查看最初的数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  1 | 张三   | 1800.00 |
|  2 | 李四   | 2000.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 删除 id 为 1 的数据
mysql> delete from emp where id=1;
Query OK, 1 row affected (0.00 sec)

-- 终端 B
-- 查看数据,看到的数据还是最初的数据,张三的记录并没有被删除
-- 这样就体现了读提交的隔离型了
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  1 | 张三   | 1800.00 |
|  2 | 李四   | 2000.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 A 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

-- 终端 B 再次查看数据就可以看到张三这条记录被删除了
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李四   | 2000.00 |
+----+--------+---------+
1 row in set (0.00 sec)

读提交的问题

-- 查看一下最初的数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李四   | 2000.00 |
+----+--------+---------+
1 row in set (0.00 sec)

-- 终端 A 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 终端 B 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 终端 A 更新和插入数据
mysql> update emp set name='李七' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> insert into emp values(3, '王五', 3333);
Query OK, 1 row affected (0.00 sec)

-- 终端 B 查看数据,由于终端 A 没有 commit,所以
-- 无法看到终端 A 修改的数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李四   | 2000.00 |
+----+--------+---------+
1 row in set (0.00 sec)

-- 终端 A 提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

-- 终端 B 看到了终端 A 修改的数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李七   | 2000.00 |
|  3 | 王五   | 3333.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 B 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

在上面的实验中,我们可以发现终端 A commit 后,终端 B 就能看到终端 A 修改的数据。那这种现象应该被允许吗?很明显,终端 B 不应该看到终端 A 修改的数据。因为终端 B 的事务还没有结束,这样就导致了一种现象:在同一个事务里,同样的读取在不同的时间端执行却读取到了不同的值。这种现象叫做不可重复读(non repeatable read)。

不可重复读会造成的问题:如果事务多次读取同一数据,但每次读取的结果都不一样,那么事务可能在处理过程中出现意外的错误,无法保持数据的一致性。

可重复读

-- 设置隔离级别为可重复读
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

-- 退出终端让隔离级别生效
mysql> quit;
Bye

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

-- 查看当前数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李七   | 2000.00 |
|  3 | 王五   | 3333.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 A 和 B 先后开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 终端 A 删除和插入数据
mysql> delete from emp where id=2;
Query OK, 1 row affected (0.00 sec)

mysql> insert into emp values(4, '赵六', 2888);
Query OK, 1 row affected (0.00 sec)

mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  3 | 王五   | 3333.00 |
|  4 | 赵六   | 2888.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 B 查看到的数据还是原来的数据,因为可重复读的隔离级别
-- 比读提交的隔离级别更高
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李七   | 2000.00 |
|  3 | 王五   | 3333.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 A 提交事务
mysql> commit;
Query OK, 0 rows affected (0.02 sec)

-- 尽管终端 A 提交了数据,终端 B 看到的还是原来的数据,也无法
-- 查看到终端 A 修改的数据,这就体现了可重复读的隔离级别了
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  2 | 李七   | 2000.00 |
|  3 | 王五   | 3333.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 B 提交事务就能够看到终端 A 修改的数据了
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  3 | 王五   | 3333.00 |
|  4 | 赵六   | 2888.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

可重复读的本质就是在一个事务内部,不受其他事务的影响,在事务执行的期间每次查到的数据都是一致的。

一般的数据库在可重复读情况的时候,无法屏蔽其
他事务 insert 的数据。为什么呢?因为隔离性实现是对数据加锁完成的,而 insert 待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题。这样会造成虽然大部分内容是可重复读的,但是 insert 的数据在可重复读情况被读取出来,导致多次查找时会多查找出来新的记录,就如同产生了幻觉。这种现象叫做幻读(phantom read)。很明显,MySQL 在 RR 级别的时候,是解决了幻读问题的。解决的方式是用 Next-Key 锁(GAP+行锁)解决的。

串行化

串行化的隔离级别会对所有操作全部加锁,进行串行化,不会有问题。但是只要是串行化,效率就会很低,几乎不会被使用。

-- 将隔离级别设置为串行化
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

-- 退出客户端,使隔离级别生效
mysql> quit;
Bye

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

-- 查看当前的数据
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  3 | 王五   | 3333.00 |
|  4 | 赵六   | 2888.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 A 先开启事务,终端 B 后开启
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 终端 A 和 B 可以同时读
mysql> select * from emp;
+----+--------+---------+
| id | name   | sal     |
+----+--------+---------+
|  3 | 王五   | 3333.00 |
|  4 | 赵六   | 2888.00 |
+----+--------+---------+
2 rows in set (0.00 sec)

-- 终端 B 执行更新操作会被阻塞住
mysql> update emp set sal=1111.1 where id=3;

-- 只有当终端 A commit 提交事务了,终端 B 才能进行更新
mysql> update emp set sal=1111.1 where id=3;
Query OK, 1 row affected (3.52 sec)
Rows matched: 1  Changed: 1  Warnings: 0

如果终端 B 比终端 A 更早开启事务,然后终端 A 要删除一条数据,那么这个操作就会被阻塞住,直到终端 B 提交了事务。

串行化会让并发性能下降,导致事务的长时间等待。

总结

  • 其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
  • 不可重复读的重点是修改和删除:同样的条件,你读取过的数据,再次读取出来发现值不一样了。
  • 幻读的重点在于新增:同样的条件,第 1 次和第 2 次读出来的记录数不一样。
  • 说明: MySQL 默认的隔离级别是可重复读,一般情况下不要修改。
  • 从上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有 commit 的时候,影响会比较大。

在这里插入图片描述

一致性

  • 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是要通过原子性来保证的。
  • 其实一致性和用户的业务逻辑强相关,一般 MySQL 提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性是由用户决定的。
  • 而技术上,通过 AID 保证 C。程序员通过 MySQL 提供的原子性、隔离性、持久性来保证业务逻辑上的一致性。

在这里插入图片描述
在这里插入图片描述

在 RR 级别的时候,多个事务对同一条数据的 update、insert 和 delete 会存在加锁现象,但是 select 和这些操作是不冲突的。这就是通过读写锁(锁有行锁或者表锁)和 MVCC(多版本并发控制)完成隔离性。

👉如何理解隔离性2👈

数据库并发的场景有三种:

  • 读-读 :不存在任何问题,也不需要并发控制。
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读。
  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失和第二类更新丢失。

读写

多版本并发控制(MVCC)是一种用来解决 读-写冲突 的无锁并发控制。MVCC 为事务分配单向增长的事务 ID,为每个修改保存一个版本,版本与事务 ID 关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题:

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。

想要理解 MVCC 需要知道三个前提知识:

  • 3 个记录隐藏字段
  • undo 日志
  • Read View

3 个记录隐藏列字段

  • DB_TRX_ID:6 Byte,最近修改(修改或插入)事务 ID,记录创建这条记录或最后一次修改该记录的事务 ID。
  • DB_ROLL_PTR:7 Byte,回滚指针指向这条记录的上一个版本。简单理解成,指向历史版本就行,这些数据一般在 undo log 中。
  • DB_ROW_ID:6 Byte,隐含的自增 ID(隐藏主键)。如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引。如果没有这个聚簇索引的话,使用普通索引就没有办法进行回表查询了。
  • 实际还有一个删除 flag 隐藏字段,即记录被更新或删除并不代表真的删除,而是删除 flag 变了。清理线程(Purge Thread)会定期去清理不需要的数据。

假设测试表结构是:

mysql> create table if not exists student(
	 > name varchar(11) not null,
	 > age int not null
	 > );
	 
mysql> insert into student (name, age) values ('张三', 28);
Query OK, 1 row affected (0.05 sec)

mysql> select * from student;
+--------+-----+
| name | age |
+--------+-----+
| 张三 | 28 |
+--------+-----+
1 row in set (0.00 sec)

上面描述的意思是:

nameageDB_TRX_ID(创建该记录的事务 ID)DB_ROW_ID(隐藏主键)DB_ROLL_PTR(回滚指针)
张三28null1null

我们目前并不知道创建该记录的事务 ID,暂时设为 null,其实它是一个具体的数字。隐式主键,我们就默认设置成 1。第一条记录也没有其他版本,我们设置回滚指针为 null。

undo 日志

undo 日志是 InnoDB 存储引擎的一个重要组成部分,用于实现多版本并发控制(MVCC)。Undo 日志是用于回滚事务和实现读一致性的关键机制,它记录了对数据进行修改前的原始数据,以便在事务回滚或其他查询需要读取一致性数据时进行使用。

以下是对 Undo 日志的详细介绍:

  • MVCC 和读一致性: 多版本并发控制(MVCC)是 InnoDB 存储引擎的默认事务隔离级别(Read Committed 和 Repeatable Read)下的并发控制机制。MVCC 允许事务在读取数据时,看到数据在事务开始之前的一个一致性快照,避免了脏读、不可重复读和幻读等问题。Undo 日志在实现 MVCC 和读一致性方面扮演重要角色。

  • Undo 日志记录: 当一个事务对数据进行修改(例如插入、更新或删除)时,InnoDB 存储引擎会在内部产生一份 Undo 日志记录,记录了修改前的原始数据。这样,如果事务需要回滚,或者其他事务需要读取旧版本的数据,就可以通过 Undo 日志来恢复到修改前的状态。

  • 回滚操作: 如果一个事务执行 ROLLBACK 操作,InnoDB 存储引擎会根据 Undo 日志中的信息,将事务所做的修改恢复到事务开始之前的状态。这样,就实现了回滚操作,将数据库恢复到之前的一致状态。

  • 读取一致性: 在 MVCC 中,读取一致性是指一个事务在执行期间读取数据的一致性快照。当事务启动时,它会创建一个 Read View,包含了当前数据库的一致性快照信息。在读取数据时,事务会根据 Read View 来判断可以读取哪些版本的数据。Undo 日志用于实现读取一致性,确保事务读取到的数据在其开始时间之前已经提交的版本。

  • 回收 Undo 日志: Undo 日志是有限的,不能无限制地增长。当事务提交后,Undo 日志中对应该事务的记录可以被标记为可回收状态。后续的事务回滚或读取一致性不再需要这些记录时,就可以回收这部分 Undo 日志空间。

Undo 日志是 InnoDB 存储引擎中用于实现多版本并发控制和读一致性的重要机制。它记录了对数据进行修改前的原始数据,在事务回滚或读取一致性时用于恢复到旧版本的数据。Undo 日志确保了数据库在并发访问时的数据一致性和正确性。由于 Undo 日志的存在,InnoDB 存储引擎能够实现高效的事务管理和并发控制,保证数据库的可靠性和性能。

如果觉得上面的解释难以理解,可以将 undo log 简单理解成是 MySQL 中的一段内存缓冲区,用来保存日志数据就行。

模拟 MVCC

假设现在有一个事务 10,对 student 表中记录进行修改(update):将 name(张三)改成 name(李四)

  • 事务10 因为要修改,所以要先给该记录加行锁。
  • 修改前,现将该行记录拷贝到 undo log 中,所以 undo log中就有了一行副本数据。(原理就是写时拷贝)
  • 所以现在 MySQL 中有两行同样的记录。现在修改原始记录中的 name,改成李四。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前事务 10 的 ID,我们默认从 10 开始,之后递增。而原始记录的回滚指针 DB_ROLL_PTR 列,里面写入 undo log 中副本数据的地址,从而指向副本记录,既表示我的上一个版本就是它。
  • 事务10 提交,释放锁。

在这里插入图片描述
备注:此时最新的记录是李四那条记录。

现在又有一个事务 11 要对 student 表中记录进行修改(update):将 age(28)改成 age(38)。

  • 事务 11 因为也要修改,所以要先给该记录加行锁。
  • 修改前,现将该行记录拷贝到 undo log 中,所以 undo log 中就又多了一行副本数据。此时新的副本,我们采用头插方式插入到 undo log 中。
  • 现在修改原始记录中的 age,改成 38。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务11 的 ID。而原始记录的回滚指针 DB_ROLL_PTR 列,里面写入 undo log 中副本数据的地址,从而指向副本记录,即表示我的上一个版本就是它。
  • 事务11提交,释放锁。

在这里插入图片描述

这样,我们就有了一个基于链表的历史版本链。所谓的回滚,无非就是用历史数据覆盖当前数据。而上面的一个个版本,我们可以称之为快照。

但是单纯从回滚的角度来看,undo log 真实的做法是保存相反的 SQL 语句。进行回滚时,将相反的 SQL 语句执行就能够完成回滚了。

上面是以更新(update)主讲的,如果是 delete 呢?一样的,删数据不是清空,而是将 flag 设置为删除即可,这样也可以形成历史版本。进行回滚操作时,将删除的反向操作执行一下就能完成回滚了。

虽然数据记录被标记为删除,但实际物理存储上的删除操作并不会立即发生。数据记录只是被标记为删除状态,而不是直接从磁盘中移除。只有在数据库执行清理任务时,才会真正地从磁盘中删除被标记为删除的数据记录,以释放空间。

如果是 insert呢?因为 insert 是插入,也就是之前没有数据,那么 insert 也就没有历史版本。但是一般为了回滚操作,insert 的反向操作也是要被放入 undo log 中。如果当前事务 commit 了,那么这个 undo log 的历史 insert 记录就可以被清空了。

undo log 中的数据是临时数据,如果只有一个事务,只要这个事务一 commit,undo log 中的数据就会被清除。但如果当前的事务 commit 了,还有其他事务访问该数据,那么 undo log 就不能清除相关的历史数据。除非,其他事务都没有访问这些数据,这样 undo log 就会把相关的数据清除。总而言之,undo log 不再需要的数据会被 MySQL 自动清掉,以节省空间。

总结一下,也就是我们可以理解成,update 和 delete可以形成版本链,insert 暂时不考虑。

那么 select 呢?首先,select 不会对数据做任何修改。所以为 select 维护多版本没有意义。不过有个问题,就是 select 读取的数据,是读取最新的版本呢?还是读取历史版本?

快照读:一般而言,读取历史版本的记录,就叫做快照读。

当前读:读取最新的记录,就是当前读。增删改操作都是对最新数据做修改,都叫做当前读。而 select 也可能当前读,比如:select in shared mode(共享锁)和 select for update。

在多个事务同时增删改的时候,都是当前读,是要加锁的。那同时有 select 过来,如果也要读取最新版(当前读),那么也就需要加锁,这就是串行化。但如果是快照读读取历史版本的话,是不用加锁限制的。也就是可以并行执行!换言之,提高了效率,这也就是 MVCC 的意义所在。而我们之前写的 select 语句大多数是快照读。

那究竟是什么决定 select 是当前读还是快照读呢?那就是由隔离级别来决定。

那为什么要有隔离级别呢?因为事务是由 begin、CURD 和 commit 组成的,也就是事务有执行前、执行中和执行后的阶段。同时,事务也有先后开启的顺序。这就决定了一个事务能够看到其他事务所做的修改,也就决定了 select 是当前读还是快照读。想要做到这样,就需要不同的隔离级别来实现。

也就是说,需要通过隔离性和隔离级别来保证事务的先后顺序问题,不应该让先开启的事务看到后开启的事务所做的修改。如果无法做到这样,也就无法保证事务的原子性了。

那么如何保证不同的事务看到不同的内容呢?也就是如何如何实现隔离级别呢?

Read View

Read View 就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID。当每个事务开启时,都会被分配一个 ID, 这个 ID 是递增的。所以越新的事务,ID 值就越大。事务的先后顺序就是根据事务 ID 来区分。

在 MySQL 源码中,Read View 就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件,用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。

注:当使用 begin 启动事务的时候,是没有 Read View 的,但是肯定有事务 ID 和事务对象。当我们首次使用 select 的时候,mysqld 就会自动给当前事务生成 Read View,并将 Read View 和事务对象绑定起来。

下面是经过简化后的 ReadView 结构:

class ReadView 
{
	// 省略...
private:
	/** 高水位,大于等于这个ID的事务均不可见*/
	trx_id_t m_low_limit_id
	/** 低水位:小于这个ID的事务均可见 */
	trx_id_t m_up_limit_id;
	/** 创建该 Read View 的事务ID*/
	trx_id_t m_creator_trx_id;
	/** 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
	trx_id_t m_low_limit_no;
	/** 标记视图是否被关闭*/
	bool m_closed;
	// 省略...
};
m_ids; // 一张列表,用来维护Read View生成时刻,系统正活跃的事务ID
up_limit_id; // 记录m_ids列表中事务ID最小的ID
low_limit_id; // ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

creator_trx_id // 创建该ReadView的事务ID

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务 ID 的,即当前记录的 DB_TRX_ID 。那么这样就让 Read View 中的事务 ID 和版本链中的记录的 DB_TRX_ID 进行对比,来决定当前能看到哪些记录。

在这里插入图片描述

  • up_limit_id 是当前事务形成快照时,还没有提交的事务 ID 的最小的。当遍历版本链时,如果 DB_TRX_ID 小于 up_limit_id,则说明创建这条记录的事务早就 commit 了,那么当前事务就能够看到这条记录。而当 DB_TRX_ID 等于 creator_trx_id 时,说明这条记录是当前事务创建的,所以当前事务也能够看到。
  • up_low_id 是当前事务 Read View 生成时,系统下一次将要分配的事务 ID,也就是目前已经出现过的事务 ID 最大值加一。虽然有些事务开启得晚,但是它进行操作的记录也会出现在版本链中。如果 DB_TRX_ID 大于等于 low_limit_id,则说明这条记录的事务比当前事务晚开启,所以当前事务不应该看到这条记录。
  • 思维误区:快照到的正在操作的事务 ID 不一定是连续的。比如:我们有 11、12、13、14 和 15 号事务,在快照之前,12 和 14 号事务已经提交了,那么 m_ids 列表就是 11、13 和 15。那么如果 DB_TRX_ID 不属于上面的两个选项,也不在 m_ids 列表中,则就说明这个事务已经提交了,当前事务可以看到这些事务。如果在 m_ids 列表中的话,说明该事务和当前事务一样是活跃事务,没有 commit,不应该看到它们所做的修改。

对应源码策略:

在这里插入图片描述
如果查到不应该看到当前版本的记录,接下来就是遍历下一个版本,直到符合条件可以看到。

整体流程

假设当前有条记录:

在这里插入图片描述
事务操作:

在这里插入图片描述
事务 4 修改 name(张三)变成 name(李四)并提交,事务 2 对某行数据执行了快照读 ,数据库为该行数据生成一个 Read View 读视图。

在这里插入图片描述
此时版本链是:

在这里插入图片描述
只有事务 4 修改过该行记录,并在事务 2 执行快照读前,就提交了事务。

在这里插入图片描述
我们的事务 2 在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id、low_limit_id 和活跃事务 ID 列表 m_ids 进行比较,判断当前事务 2 是否能看到该记录的版本。

在这里插入图片描述

实验演示

在这里插入图片描述

RR 与 RC 的本质区别

当前读和快照读在 RR 级别下的区别

-- 在 RR 级别下进行测试
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

-- 退出终端使隔离级别生效
mysql> quit;
Bye

-- 查看隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

-- 查看原始数据
mysql> select * from student;
+--------+-----+
| name   | age |
+--------+-----+
| 李四   |  28 |
| 王五   |  48 |
+--------+-----+
2 rows in set (0.00 sec)

测试用例 1

事务 A 操作事务 A 描述事务 B 描述事务 B 操作
begin开启事务开启事务begin
select * from student;快照读形成 Read View快照读形成 Read Viewselect * from student;
update student set age=18 where name=‘李四’;将李四的年龄更新为 18
commit;提交事务
select 快照读,没有读到最新数据select * from student;
当前读,读到了最新数据select * from student lock in share mode;

因为快照读并没有读取到事务 A 所做的修改,这就体现出来隔离性。而 select * from student lock in share mode ,以加共享锁方式进行读取,对应的就是当前读,能够读取到最新的数据。

在这里插入图片描述
测试用例 2

事务 A 操作事务 A 描述事务 B 描述事务 B 操作
begin开启事务开启事务begin
select * from student;快照读形成 Read View,查到李四的年龄为 18
update student set age=58 where name=‘李四’;将李四的年龄更新为 58
commit;提交事务
select 快照读形成 Read View,查到李四的年龄为 58select * from student;
当前读,查到李四的年龄为 58select * from student lock in share mode;

在这里插入图片描述
用例 1 和用例 2 的唯一区别仅仅是用例 1 的事务 B 在事务 A 修改 age 之前快照读过一次 age 数据,而用例 2 的事务 B 在事务 A 修改 age 之前没有进行过快照读。

结论:

  • 几乎同时启动的事务,后者是否能够看到前者修改并提交的数据,取决于后者什么时候进行快照读。因为快照读就会形成 Read View,然后根据 Read View 中的各字段与 DB_TRX_ID 的对比结果,来决定当前事务能够看到什么数据。
  • 也就是说,事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读,决定该事务后续快照读结果的能力。对于增加 insert 和删除 delete 也是如此。
  • 读写并发是由 MVCC 来提供支撑的,因为有历史版本,快照读就不会影响增删改,也就能够提高读写效率。而有了历史版本,才能给隔离性提供功能支持。

RR 与 RC 的本质区别

  • 正是 Read View 生成时机的不同,从而造成 RC 和 RR 级别下快照读的结果的不同。
  • 在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及 Read View,将当前系统活跃的其他事务记录起来。此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个 Read View,所以对之后的修改不可见。
  • 也就是说,RR 级别下快照读生成 Read View 时,Read View 会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于 Read View 创建的已经提交的事务所做的修改均是可见。
  • 而在 RC 级别下的,事务每次快照读都会新生成一个快照和 Read View,这就是我们在 RC 级别下的事务中可以看到别的事务提交的更新的原因。
  • 总之在 RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR 隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View,之后的快照读获取的都是同一个 Read View。
  • 正是 RC 每次快照读都会形成 Read View,所以 RC 才会有不可重复读问题。

概念补充

bin log

二进制日志(Binary Log,简称 bin log)是一种记录数据库变更操作的日志文件。它记录了对数据库进行的插入、更新、删除等操作的详细信息,用于数据的备份、复制和恢复。二进制日志是 MySQL 主从复制、数据恢复以及数据库高可用性的关键组成部分。

以下是对二进制日志(bin log)的详细介绍:

  • 日志格式: 二进制日志有三种常见的日志格式:Statement、Row 和 Mixed。每种格式有不同的记录方式:

    • Statement 格式:记录执行的 SQL 语句,可以通过重新执行相同的语句来恢复数据。但是由于某些语句的执行结果可能受到随机函数等因素的影响,因此在一些情况下可能会导致主从数据不一致。
    • Row 格式:记录每一行数据的变更情况,更安全,但日志量较大。
    • Mixed 格式:混合使用 Statement 和 Row 格式,根据具体情况选择最合适的日志记录方式。
  • 主从复制: 二进制日志在 MySQL 主从复制中扮演重要角色。主服务器将变更操作记录在二进制日志中,从服务器连接到主服务器,并从二进制日志中读取变更操作,然后在从服务器上执行相同的操作,从而实现数据的复制和同步。

  • 数据备份和恢复: 通过二进制日志,可以将数据库的变更操作记录下来,从而实现数据的备份和恢复。通过备份二进制日志,可以将数据库还原到特定的时间点,实现精确的数据恢复。

  • 数据的增量备份: 在数据量较大的情况下,全量备份耗时较长且占用较大的存储空间。使用二进制日志进行增量备份可以节省备份时间和存储空间,只备份从上次全量备份以来的变更操作。

  • 数据库的高可用性: 二进制日志在数据库的高可用性方面起到关键作用。通过将二进制日志传输到多个备份服务器,可以实现数据库的多点部署,提高系统的可用性和容错性。

redo log

重做日志(Redo Log)是一种用于保证事务持久性的日志文件。它记录了对数据库进行的修改操作,主要用于在数据库崩溃或故障后,将未提交的事务重新应用到数据库,以确保数据的持久性和一致性。

以下是对重做日志(Redo Log)的详细介绍:

  • 事务持久性: 事务持久性是数据库系统的一个重要特性,它确保了在数据库崩溃或故障后,已提交的事务不会丢失,数据库能够在恢复后保持一致的状态。重做日志是实现事务持久性的关键机制。

  • 日志记录方式: 重做日志采用追加(Append-only)的方式记录对数据库的修改操作。当事务执行修改操作(如插入、更新或删除)时,相应的修改信息会被写入重做日志,但并不立即将修改应用到数据库文件中,而是先记录在重做日志中。

  • 磁盘写入策略: 为了保证事务持久性,重做日志的写入采用了一种“写入即持久”的策略。即每次事务提交时,重做日志必须先将修改操作写入磁盘,确认写入成功后才会返回事务提交成功,保证修改的持久性。

  • 循环写入: 重做日志是一个循环写入的日志结构,当重做日志写满后,会从头部循环写入,覆盖之前的内容。这就意味着只要数据在数据库中持久化,对应的重做日志中的记录都是不再需要的。

  • 崩溃恢复: 在数据库崩溃或故障后,MySQL 在启动时会检查重做日志,将未提交的事务重新应用到数据库,恢复到崩溃前的一致状态。重做日志的这种功能称为崩溃恢复(Crash Recovery)。

全量备份和增量备份

全量备份

全量备份是指备份整个数据库的所有数据和对象,包括所有表、索引、存储过程、触发器等。完成全量备份的时间开销较大,特别是在数据量较大的情况下,备份时间可能较长。全量备份适用于数据量不是很大,备份频率较低的情况下,或作为初始化备份的方式。

增量备份

增量备份是指备份自上次全量备份以来发生修改的数据部分,只备份变动的部分数据,而不是整个数据库。完成增量备份的时间开销相对较小,因为只备份了部分数据。增量备份适用于数据量较大,备份频率较高的情况下,它可以节省存储空间和备份时间。

推荐阅读

https://www.jianshu.com/p/398d788e1083
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://www.cnblogs.com/aspirant/p/9177978.html
https://blog.csdn.net/SnailMann/article/details/94724197
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html
https://blog.csdn.net/chenghan_yang/article/details/97630626

👉总结👈

本篇博客主要解决了什么是事务、为什么要有事务、事务的应用场景、事务的特征、事务的隔离级别、不同隔离级别的问题、Read View、版本链、RR 与 RC 的本质区别等等。以上就是本篇博客的全部内容,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!💖💝❣️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/809131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用mediapipe训练手指数字识别

mediapipe手指数字识别 本文是从0开始创建一个识别手势的机器学习模型,为了识别手势,采用mediapipe模型,这个模型会返回手指的位置,之后再通过训练一个模型将这些位置分类得到手势 一、导入依赖 import cv2 import numpy as np…

分库分表之基于Shardingjdbc+docker+mysql主从架构实现读写分离(一)

说明:请先自行安装好docker再来看本篇文章,本篇文章主要实现通过使用docker部署mysql实现读写分离,并连接数据库测试。第二篇将实现使用Shardingjdbc实现springboot的读写分离实现。 基于Docker去创建Mysql的主从架构 #创建主从数据库文件夹…

小黑子—JavaWeb:第四章 Request与Response

JavaWeb入门4.0 1. Request(请求)& Response (响应)2. Request2.1 Request 继承体系2.2 Request 获取请求数据2.2.1 通用方式获取请求参数2.2.2 IDEA模板创建Servlet2.2.3 请求参数中文乱码处理2.2.3 - I POST解决方案2.2.3 - II GET解决方案 2.3 Request 请求转发 3. Resp…

常见网关对比

常见网关对比 目前常见的开源网关大致上按照语言分类有如下几类: Nginxlua :OpenResty、Kong、Orange、Abtesting gateway 等 Java :Zuul/Zuul2、Spring Cloud Gateway、Kaazing KWG、gravitee、Dromara soul 等 Go :Janus、fa…

简单学会MyBatis原生API注解

😀前言 本篇博文是关于MyBatis原生API&注解的使用,希望能够帮助到你😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您…

2023深圳杯数学建模B题

B题 电子资源版权保护问题 版权又称著作权,包括发表权、署名权、修改权、保护作品完整权、复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权及应当由著作权人享有的其他权利。 在计算机网络广泛应用的今…

39.手机导航

手机导航 html部分 <div class"phone"><div class"content"><img class"active" src"./static/20180529205331_yhGyf.jpeg" alt"" srcset""><img src"./static/20190214214253_hsjqw…

基于 ThinkPHP 5.1(稳定版本) 开发wms 进销存系统源码

基于ThinkPHP 5.1&#xff08;LTS版本&#xff09;开发的WMS进销存系统源码 管理员账号密码&#xff1a;admin 一、项目简介 这个系统是一个基于ThinkPHP框架的WMS进销存系统。 二、实现功能 控制台 – 权限管理&#xff08;用户管理、角色管理、节点管理&#xff09; – 订…

BUUCTF——reverse3 适合新手的关于base64加密算法代码的分析

作为一个逆向小白&#xff0c;学了点加密算法就来BUU找点乐子&#xff0c;前7题蛮简单的&#xff0c;然后做到了reverse3&#xff0c;典型的base64加密算法&#xff0c;让我折腾了好久&#xff0c;写篇博客记录一下 顺便说下很多博客并没有对这里的加密算法进行代码上的分析&a…

秋招备战笔试Day1

目录 单选 1. 在 Java 中&#xff0c;存放字符串常量的对象属于&#xff08; &#xff09;类对象。 2.已知如下类定义&#xff1a; 如下哪句可以正确地加入子类中&#xff1f; 3. 下列选项中属于面向对象编程主要特征的是&#xff08;&#xff09; 4.关于下列程序段的输出结…

并发编程可能出现的核心问题

2.1非可见性 如果主内存里有个静态变量flagfalse&#xff0c;然后线程A和B在工作内存都需要操作flag&#xff0c;线程A是while(!false){}&#xff0c;而线程B将flag改为true&#xff0c;但是由于线程A和线程B之间工作内存互相不可见&#xff0c;线程A就会陷入死循环。 2.2指令…

排序链表——力扣148

文章目录 题目描述法一 自顶向下归并排序法二&#xff09;自底向上归并排序 题目描述 题目的进阶问题要求达到 O(nlogn) 的时间复杂度和 O(1) 的空间复杂度&#xff0c;时间复杂度是 O(nlogn) 的排序算法包括归并排序、堆排序和快速排序&#xff08;快速排序的最差时间复杂度是…

【RabbitMQ(day2)】默认(直连)交换机的应用

文章目录 一、第一种模型&#xff08;Hello World&#xff09;二、第二种模型&#xff08;work queue&#xff09;自动确认机制的后果和公平分配 三、阐述默认交换机 这篇博客是以下资料学后的总结&#xff1a; 不良人的RabbitMQ的教学视频 官方启动教程 RabbitMQ中文文档 一、…

php 生成连续递增的Excel列索引 可以控制多少列

今天遇到需要生成对应的下拉&#xff0c;下拉的类 需要PHP 输出一个数组 如 A、B、C、D 到Z 列后 Excel 的列就变成 AA 、AB、 AC 依次类推 查询得知 Excel 最大列数 16384 最大行数 1048576 下面演示3000列或行 <?php$idx [idx > 0];for ($i …

WIZnet W6100-EVB-Pico 静态IP配置教程(二)

W6100是全球第一款支持IPv4/IPv6双核的新一代全硬件以太网TCP/IP协议栈控制器。W6100在WIZnet核心专利技术——全硬件TCP/IP协议栈IPv4的基础上增加了IPv6&#xff0c;解决了嵌入式以太网的接入问题&#xff0c;简单易用&#xff0c;安全稳定&#xff0c;是物联网设备的首选解决…

某文化馆三维建模模型-glb格式-三维漫游-室内导航测试

资源描述 某文化馆某个楼层的三维建模模型&#xff0c;glb格式&#xff0c;适用于three.js开发&#xff0c;可用来做一些三维室内漫游测试和室内导航测试 资源下载地址

Java框架学习(三)spring5高级49讲

文章目录 1、BeanFactory与ApplicationContext2、BeanFactory与ApplicationContext的容器实现BeanFactory的容器实现后处理器排序 ApplicationContext的容器实现 3、Bean的生命周期Bean后处理器 4、常见的Bean后处理器5、常见BeanFactory后处理器6、Aware和InitializingBean接口…

移动零——力扣283

题目描述 双指针 class Solution{ public:void moveZeroes(vector<int>& nums){int n nums.size(), left0, right0;while(right<n){if(nums[right]){swap(nums[right], nums[left]);left;}right;}} };

Golang之路---02 基础语法——常量 (包括特殊常量iota)

常量 //显式类型定义const a string "test" //隐式类型定义const b 20 //多个常量定义 const(c "test2"d 2.3e 27)iota iota是Golang语言的常量计数器&#xff0c;只能在常量表达式中使用 iota在const关键字出现时将被重置为0&#xff0c;const中每新…

Flowable-任务-接受任务

定义 接收任务是一种简单任务&#xff0c;它会等待对应消息的到达。当流程执行到达接收任务时&#xff0c;流程状态会持 久化到数据库中&#xff0c;这意味着该流程将一直处于等待状态&#xff0c;直到引擎接收到一个特定的消息为止&#xff0c;该消息 将触发离开接收任务继续…