当前位置:   article > 正文

MySQL事务详解_mysql 事务

mysql 事务

目录

一.什么是事务

二.事务的特性

三.MySQL使用事务

1. 开启事务

2. 执行SQL语句

3. 回滚事务

4. 提交事务

四.InnoDB 事务的ACID如何保证

(1)基本概念

(2)REDO LOG

1)REDO LOG是什么

2)redo日志位置

3)redo buffer

4)redo的刷新策略

5)MySQL CSR——前滚

(3)undo 回滚日志

1)undo是什么

2)undo回滚日志的作用

五.事物的隔离级别

1.脏读

2.不可重复读

3.幻读

幻读与不可重复读的区别

幻读的影响

幻读产生的原因

如何解决幻读

4.事物的隔离级别


一.什么是事务

事务(Transaction)是在数据库管理系统中执行的一个逻辑操作单元,它是由一系列数据库操作组成的逻辑工作单元。事务是并发控制的单位,是用户定义的一个操作序列。事务必须满足:原子性、一致性、隔离性和持久性四个特性(ACID)。事务可以防止多个用户同时对数据库进行修改时产生的数据不一致问题,例如脏读、不可重复读和幻读。事务的隔离级别有四种,分别是读未提交、读提交、可重复读和串行化,不同的隔离级别有不同的并发效果和开销。事务可以用SQL语句或编程语言来控制,例如BEGIN、COMMIT、ROLLBACK等。事务使用的常见操作包括插入(Insert)、更新(Update)、删除(Delete)等。

二。事务的特性

1. 原子性(Atomicity):事务是一个不可分割的操作单元,要么完全执行,要么完全不执行。如果事务中的某个操作失败,那么整个事务都将被撤销,回滚到事务开始前的状态。

2. 一致性(Consistency):事务在执行之前和执行之后,数据库的状态必须保持一致。这意味着事务执行过程中的任何变化都必须满足预定的规则和约束。

3. 隔离性(Isolation):事务的执行应该与其他事务的执行相互隔离,即每个事务的操作独立于其他事务的操作。这确保了事务在并发执行时,不会相互干扰导致数据不一致或异常结果。

4. 持久性(Durability):一旦事务被提交,其所做的更改将永久保存在数据库中,并且在系统故障或重启后仍然保持有效。

事务的使用可以保证数据库在进行多个操作时的一致性和可靠性。如果某些操作依赖于其他操作的结果,或者需要保证数据的完整性和一致性,那么使用事务可以确保这些需求得到满足。

特性例子结果
原子性张三给李四转账100元,扣减张三账户余额和增加李四账户余额是两个操作要么两个操作都成功,要么两个操作都失败
一致性张三给李四转账100元,转账前后两人账户总额不变转账成功后,张三账户少了100元,李四账户多了100元
隔离性张三给李四转账100元,同时王五查询李四账户余额王五只能查询到转账前或转账后的余额,不能查询到中间状态
持久性张三给李四转账100元,转账成功后数据库崩溃数据库恢复后,李四仍然能看到多了100元

三。MySQL使用事务

1. 开启事务

使用 `START TRANSACTION` 或者 `BEGIN` 命令来开始一个新的事务。示例:

START TRANSACTION;

  或者

BEGIN;

        在你执行了这个命令后,MySQL会将接下来的所有语句视为事务的一部分,直到你提交或者回滚事务。

2. 执行SQL语句

在事务中执行你的SQL语句。例如,你可以插入、更新或删除数据库中的数据。

3. 回滚事务

如果在事务执行过程中出现了错误,你可以选择回滚事务以撤销所有更改并恢复到事务开始的状态。使用 `ROLLBACK` 命令来回滚当前的事务。示例:

ROLLBACK;

    这将撤销事务中的所有更改,并且回滚到事务开始之前的状态。

4. 提交事务

如果事务中的所有操作都成功完成,并且你希望将更改永久保存到数据库中,可以使用 `COMMIT` 命令来提交事务。示例:

COMMIT;

    这将使事务的更改永久生效,并将它们保存到数据库中。

注:默认情况下,MySQL处于自动提交模式,即每个语句都被视为一个事务,并自动提交到数据库。如果要手动管理事务,你可以禁用自动提交模式,使用 `SET AUTOCOMMIT=0;` 命令来禁用自动提交,并在需要时使用开启、回滚和提交事务的命令来管理事务的生命周期。

  • 自动提交是否打开,一般在有事务需求的MySQL中,将其关闭
  • 不管有没有事务需求,我们一般也都建议设置为0,可以很大程度上提高数据库性能

    此外,还可以使用 `SAVEPOINT` 和 `RELEASE SAVEPOINT` 来创建和释放保存点,以便在事务中实现部分回滚。

  1. SAVEPOINT
  2. RELEASE SAVEPOINT

使用`ROLLBACK TO SAVEPOINT`命令可以回滚到指定的保存点。

ROLLBACK TO SAVEPOINT

这些命令可以提供更细粒度的事务管理和控制。

四。InnoDB 事务的ACID如何保证

(1)基本概念

1. 重做日志 (Redo Log): 重做日志是一种用于持久化数据更改的机制。在MySQL中,重做日志以循环方式由多个文件(例如 ib_logfile0 和 ib_logfile1)组成,每个文件的大小通常为50MB。重做日志记录了对数据库进行的物理更改,例如插入、更新和删除操作。

2. Redo Log Buffer: 重做日志缓冲区是一个位于内存中的区域,用于临时存储正在进行的事务的重做日志条目。当事务执行修改操作时,相关的重做日志条目会先被写入重做日志缓冲区,然后再由后台线程异步地刷新到磁盘的重做日志文件中。

3. InnoDB 存储引擎: InnoDB是MySQL中一种常用的存储引擎,它负责管理数据行和索引的存储。每个表在InnoDB中对应一个以 .ibd 后缀结尾的文件,其中包含了存储在磁盘上的数据行和索引。

4. Buffer Pool: 缓冲池是InnoDB在内存中缓存数据库中的数据和索引页的区域。它是一个用于高效读取和写入数据的内存区域。当需要读取或写入数据时,InnoDB首先会检查缓冲池中是否存在对应的数据页,如果存在则直接操作内存中的数据页,提高访问速度。

5. LSN (Log Sequence Number): 日志序列号是用于标识和跟踪重做日志的顺序的唯一递增值。LSN用于确保数据的一致性,并在MySQL启动时进行检查,以确保磁盘上的数据与重做日志的内容一致。

6. WAL (Write Ahead Log): WAL是一种日志优先写的策略,确保在将数据页刷新到磁盘之前,相关的日志先被持久化到磁盘中。这种策略可以保证在发生系统故障时,可以通过重做日志将数据恢复到故障前的一致状态。

7. 脏页 (Dirty Page): 脏页是指在内存中已经被修改过但尚未写入磁盘的数据页。当数据被修改后,相应的内存页会被标记为脏页,这样在后续的时间里,系统可以将其异步刷写到磁盘,以保持内存和磁盘之间的数据一致性。

8. Checkpoint (CKPT): 检查点是指定时将内存中的脏页刷写到磁盘的操作。通过执行检查点,可以将脏页的修改持久化到磁盘,以减少系统故障时的数据损失。

9. TXID (Transaction ID): 事务号是InnoDB为每个事务分配的唯一标识符。事务号伴随整个事务的执行,用于跟踪和管理事务的一致性。

(2)REDO LOG

1)REDO LOG是什么

REDO LOG(重做日志)是MySQL中一种用于持久化数据修改的机制。它记录了对数据库进行的物理更改操作,如插入、更新和删除。

重做日志的主要目的是确保在发生数据库崩溃、系统故障或意外断电等情况下,数据库可以恢复到最后一次正常运行时的状态。通过重做日志,MySQL可以在数据库重新启动时应用那些尚未写入磁盘的已提交事务的修改,从而保持数据的一致性。

重做日志是以循环方式组织的,通常由多个日志文件组成(如 `ib_logfile0`、`ib_logfile1` 等)。每个日志文件的大小通常设定为一定的固定值(如50MB)。当一个文件被填满后,新的写入会继续在下一个日志文件中进行,这样就实现了循环使用。

在事务进行数据修改时,相关的重做日志记录会先写入内存中的重做日志缓冲区(redo log buffer),然后由后台线程异步地将其刷新到磁盘上的重做日志文件中。这种异步刷新的方式能够提高数据库的性能,并减少对磁盘的频繁写入。

当MySQL数据库重新启动时,会检查磁盘上最后一个被写入的重做日志文件及其偏移量。系统会根据这些信息确定从哪个点开始进行崩溃恢复,将未应用的重做日志中的修改重新应用到数据库,以确保数据的完整性和一致性。

2)redo日志位置

在MySQL中,重做日志(Redo Log)的位置在数据目录中的日志文件中。具体位置取决于你的MySQL配置和操作系统。

默认情况下,重做日志文件名为 `ib_logfile0` 和 `ib_logfile1`,它们位于数据目录下。数据目录的路径在MySQL配置文件(通常是 `my.cnf` 或 `my.ini`)中的 `datadir` 参数中指定。

你可以通过登录到MySQL服务器并执行以下命令来查找重做日志文件的位置:

SHOW VARIABLES LIKE 'datadir';

执行此命令会显示MySQL数据目录的路径,你可以在该路径下找到 `ib_logfile0` 和 `ib_logfile1` 文件。

注意,根据你的MySQL配置,可能还有其他的重做日志文件,它们可能命名为 `ib_logfile2`、`ib_logfile3` 等。这些文件都是循环使用的,当一个文件被填满后,MySQL会继续使用下一个文件,以此类推。

3)redo buffer

重做缓冲区(Redo Buffer)是一个用于暂时存放数据页的变化信息和对应LSN号的内存区域。它是用于临时存储正在进行的事务的重做日志记录的地方。它可以帮助提高数据库系统的性能。而LSN是一种用于唯一标识数据库日志记录的序列号,用于跟踪和管理数据库的一致性和持久性。

  • LSN(日志序列号)是一个递增的唯一标识符,用于标记数据库的每个日志记录。LSN可以在数据库中的多个地方使用,包括磁盘上的数据页、内存中的数据页、redo buffer和redo log(重做日志)。通过比较LSN的值,数据库系统可以确定哪些日志记录已经被应用到数据页,以及哪些日志记录还没有。

在事务执行期间,如果有数据修改操作(例如插入、更新或删除),相应的重做日志条目会首先写入到重做缓冲区中,然后再由后台线程异步地将它们刷新到磁盘上的重做日志文件中。

重做缓冲区的设计有助于提高数据库的性能和并发处理能力。将重做日志条目首先写入内存中的缓冲区,可以避免频繁地访问和写入磁盘,从而提高了数据修改操作的处理速度。

此外,重做缓冲区还是实现数据库事务的原子性和持久性的关键组件之一。在事务提交时,MySQL会将重做缓冲区中的相关日志条目写入到磁盘的重做日志文件中,以确保已提交的事务的修改持久化到磁盘,从而保证数据的一致性和持久性。

需要注意的是,重做缓冲区的大小是有限的,如果正在进行的事务写入的重做日志超过了缓冲区的容量,MySQL就会进行强制性的刷新操作,以确保重做日志能够持续写入到磁盘上的重做日志文件中。

4)redo的刷新策略

在数据库系统中,为了确保事务的持久性(durability)和原子性(atomicity),会使用事务日志来记录每个事务的操作。当事务进行提交(commit)操作时,系统会将事务日志中的变更(redo buffer)刷新(flush)到磁盘中的持久存储介质,通常是硬盘。

刷新redo buffer的目的是将已经提交的事务持久化到磁盘,以确保在任何系统崩溃或断电的情况下,可以通过日志重放(log replay)的方式将未来的数据状态重新恢复到崩溃之前的一致状态。

在commit时,除了刷新当前事务的redo log buffer到磁盘外,还会顺便将一部分redo log buffer中没有提交的事务日志也刷新到磁盘。这是为了提高性能和减少磁盘IO操作。但是这些没有提交的事务日志并不会影响数据的一致性,因为它们都有对应的事务号和LSN(日志序列号),在MySQL启动时或者崩溃恢复时,会根据这些信息来判断哪些事务需要回滚或者前滚。

redo log的刷新策略是指在事务提交时,如何将redo log buffer中的日志写入到redo log file中。MySQL提供了一个参数innodb_flush_log_at_trx_commit来控制这个策略,它有三个可选的值:

  • 0:表示每秒将redo log buffer中的日志写入到OS buffer,并调用fsync()函数将OS buffer中的日志刷入到redo log file中。这种方式性能较好,但是如果系统崩溃,可能会丢失最近一秒内的数据。
  • 1:表示每次事务提交时,都将redo log buffer中的日志写入到OS buffer,并调用fsync()函数将OS buffer中的日志刷入到redo log file中。这种方式数据安全性最高,但是性能较差,因为需要频繁地进行磁盘IO操作。
  • 2:表示每次事务提交时,都将redo log buffer中的日志写入到OS buffer,但是不调用fsync()函数,而是每秒调用一次fsync()函数将OS buffer中的日志刷入到redo log file中。这种方式性能较好,但是如果系统崩溃或者电源故障,可能会丢失部分数据。

5)MySQL CSR——前滚

MySQL : 在启动时,必须保证redo日志文件和数据文件LSN必须一致, 如果不一致就会触发CSR,最终保证一致

情况一

我们做了一个事务,begin;update;commit.

1. 在begin ,会立即分配一个TXID=tx_01.

2. update时,会将需要修改的数据页(dp_01,LSN=101),加载到data buffer中

3. DBWR线程,会进行dp_01数据页修改更新,并更新LSN=102

4. LOGBWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redobuffer

5. 执行commit时,LGWR日志写线程会将redobuffer信息写入redolog日志文件中,基于WAL原则,在日志完 全写入磁盘后,commit命令才执行成功,(会将此日志打上commit标记)

6. 假如此时宕机,内存脏页没有来得及写入磁盘,内存数据全部丢失

7. MySQL再次重启时,必须要redolog和磁盘数据页的LSN是一致的.但是,此时dp_01,TXID=tx_01磁盘是 LSN=101,dp_01,TXID=tx_01,redolog中LSN=102

MySQL此时无法正常启动,MySQL触发CSR.在内存追平LSN号,触发ckpt,将内存数据页更新到磁盘,从而保证 磁盘数据页和redolog LSN一值.这时MySQL正长启动 以上的工作过程,我们把它称之为基于REDO的"前滚操作"

(3)undo 回滚日志

1)undo是什么

当一个事务执行过程中发生错误或者被中断,需要回滚(Rollback)到事务开始前的状态。这意味着要撤销已经执行的操作,将数据库恢复到事务开始之前的一致状态。

为了支持回滚操作,数据库系统使用undo回滚日志。当数据库执行一个事务时,每个被修改的数据页的旧值会被记录在undo回滚日志中。这样,如果需要回滚事务,就可以使用undo日志中的旧值来恢复数据页到事务开始前的状态。

2)undo回滚日志的作用

undo(回滚)的作用是保证事务的原子性和隔离性,同时支持快照技术和数据恢复。

  • 原子性(Atomicity):通过undo,可以在事务回滚时将数据恢复到修改之前的状态,确保事务要么完全执行,要么完全回滚,避免了部分操作的影响。
  • 隔离性(Isolation):并发事务可能同时修改同一个数据,使用undo可以提供事务的私有数据版本,保证每个事务独立地看到修改之前的数据,从而实现隔离性。
  • 快照技术:undo提供了事务修改之前的数据状态的快照,这意味着在事务执行过程中,可以回退到之前的状态。这对于实现多版本并发控制(MVCC)非常重要,每个事务可以读取数据的一致版本,而不会受到其他事务的修改影响。
  • 数据恢复:undo日志记录了事务的修改操作和之前的数据状态,当系统发生故障或崩溃时,可以使用undo日志来恢复未提交的事务或未完成的操作。这种数据恢复机制对于数据库的持久性和可靠性非常重要。

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含 3 个隐藏列

  • db_row_id:这是一个隐藏的行ID列。当表没有定义自定义主键或唯一键时,InnoDB会自动在聚簇索引记录中添加这个隐藏列作为主键。它用于唯一标识每一行数据。

  • db_trx_id:这是操作数据的事务ID列。它记录了最后一个对该数据进行插入或更新的事务的ID。这个列用于支持多版本并发控制(MVCC)和事务的隔离性。

  • db_roll_ptr:这是回滚指针列,它指向与这个记录相关的Undo Log(撤销日志)信息。Undo Log中存储了在事务回滚时需要用到的旧值和恢复数据的信息。这个列在数据恢复和事务回滚时起着重要的作用

五。事物的隔离级别

多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据 时的准确性。 如果不考虑隔离性,可能会引发如下问题:

  1. 幻想读
  2. 不可重复读取
  3. 脏读

1.脏读

脏读是指在数据库访问中,一个事务读取了另一个事务未提交的数据,导致读取到的数据是不一致或者无效的。脏读一般是针对于update操作的。脏读可能会导致数据逻辑错误或者业务异常。

示例:

假设有两个事务A和B,并且使用默认的隔离级别(可重复读)。事务A修改了一条数据的值为A,但是还没有提交。事务B读取了这条数据的值为A,并基于该值进行了一些业务处理。此时,如果事务A回滚了,事务B读取到的数据就是无效的,而且基于该数据的业务处理可能出错。

事务A的操作:

  1. START TRANSACTION;
  2. UPDATE your_table SET value = 'A' WHERE id = 1;
  3. -- 不提交事务,保持事务处于未提交状态

事务B的操作:

  1. START TRANSACTION;
  2. SELECT value FROM your_table WHERE id = 1;
  3. -- 基于读取到的值进行业务处理

事务A使用`ROLLBACK;`回滚。

ROLLBACK;

2.不可重复读

不可重复读是指在一个事务内,多次读取同一数据,但是数据的值发生了改变。这种现象是由于其他事务在这个事务多次读取的过程中,对数据进行了更新并提交,导致这个事务每次读取的结果不一致。不可重复读针对的是已经提交的数据,和脏读的区别是脏读针对的是未提交的数据。

示例:

假设有两个事务A和B,事务A第一次查询一条数据的值为A,然后事务B更新了这条数据的值为B,并且提交了事务,此时事务A第二次查询这条数据,发现值变成了B。这就是不可重复读的情况。

事务A的操作代码如下:

  1. START TRANSACTION;
  2. SELECT value FROM example WHERE id = 1;
  3. -- 第一次查询,得到的值为A
  4. -- 事务暂停一段时间,等待事务B的操作完成
  5. SELECT value FROM example WHERE id = 1;
  6. -- 第二次查询,得到的值为B

事务B的操作代码如下:

  1. START TRANSACTION;
  2. UPDATE example SET value = 'B' WHERE id = 1;
  3. COMMIT;

在事务A第一次查询到值为A后,事务B对该数据进行了更新并提交事务。在事务A第二次查询时,发现值已经变成了B,产生了不可重复读的现象。

3.幻读

幻读是一种数据库事务的并发问题,指的是在一个事务中,多次查询同一个范围的数据,却发现有新增或者减少的行。这是因为在这个事务进行的过程中,另一个事务插入或者删除了符合查询条件的数据,导致前后两次查询结果不一致。

以下是一个简单的示例,模拟幻读的情况:

事务A的操作代码如下:

  1. START TRANSACTION;
  2. SELECT * FROM example WHERE value LIKE 'A%';
  3. -- 第一次查询,得到的结果集包含满足条件的所有行
  4. -- 事务暂停一段时间,等待事务B的操作完成
  5. SELECT * FROM example WHERE value LIKE 'A%';
  6. -- 第二次查询,得到的结果集可能包含新增的满足条件的行

事务B的操作代码如下:

  1. START TRANSACTION;
  2. INSERT INTO example (id, value) VALUES (100, 'Apple');
  3. COMMIT;

在事务A第一次查询后,事务B插入了一行数据,符合事务A的查询条件。在事务A第二次查询时,得到的结果集可能会包含新增的符合条件的行,产生了幻读的现象。

幻读与不可重复读的区别

不可重复读强调的是同一行数据在事务过程中的变化,而幻读则强调的是在同一条件下结果集行数的变化。不可重复读是由于数据的更新操作引起的,而幻读则是由于数据的插入或删除操作引起的。

幻读的影响

幻读的影响主要有以下几个方面:

  • 幻读会导致事务执行出现意料之外的结果,例如事务1查询主键为a的记录,如果不存在,则插入。假如事务1查询主键为a的记录不存在时,事务2插入了主键为a的记录并提交,那么事务1执行插入语句时,会发生主键冲突的错误。
  • 幻读会导致数据库中的数据和binlog的执行结果不一致,例如事务1查询usercnt为0的记录,并更新为100。假如事务1查询usercnt为0的记录时,事务2更新了id为4的记录,将usercnt从4改为0,并提交。那么事务1提交后,表中的数据是:
idownerusercnt
00100
44100

但是binlog中的日志是:

  1. update game_test set usercnt = 0 where id = 4;
  2. update game_test set usercnt = 100 where usercnt = 0;

如果按照binlog将数据同步到备库中,那么备库中的数据是:

idownerusercnt
00100
440

这样就导致了主备库数据不一致。

幻读产生的原因

幻读产生的原因主要有以下几个方面:

  • 幻读是由于在可重复读的隔离级别下,当前读(加锁的查询或者数据修改操作)可以看到其他事务插入的新数据,而快照读(普通的查询)却看不到,导致同一个事务中两次查询结果不一致。
  • 幻读是由于行锁只能锁住已存在的行,而不能阻止其他事务在锁定范围内插入新的行,导致同一个事务中两次查询结果集的数量不一致。
  • 幻读是由于在可重复读的隔离级别下,undo log中记录了数据修改操作的事务ID,而在查询时,如果发现undo log中的事务ID与当前事务ID相等,就会认为该数据对当前事务可见,导致同一个事务中两次查询结果集的内容不一致。

如何解决幻读

解决幻读的方法主要有以下几个方面:

  • 使用   MVCC(多版本并发控制)机制,让每个事务在快照读(普通的SELECT语句)时,只能看到自己事务开始时的数据版本,而不受其他事务插入的新数据影响。¹
  • 使用   间隙锁(Gap Lock)机制,让每个事务在当前读(加锁的查询或者数据修改操作)时,对查询范围内的所有记录和间隙加上锁,阻止其他事务在锁定范围内插入新的数据。²
  • 使用   Next-Key Lock机制,结合间隙锁和行锁,对查询范围内的所有记录和间隙加上锁,并且对扫描到的记录加上行锁,防止其他事务修改或删除已存在的记录。³
  • 使用   串行化(Serializable)隔离级别,让每个事务在执行前加上表级共享锁,防止其他事务对表进行任何修改操作。

4.事物的隔离级别

事务的隔离级别是指在并发事务中,一个事务对数据的修改是否对其他事务可见,以及其他事务对数据的修改是否对当前事务可见。不同的隔离级别会导致不同的并发问题,如脏读、不可重复读、幻读等。

SQL标准定义了四种隔离级别,由低到高分别是:

  • 读未提交(Read Uncommitted):一个事务可以读取另一个未提交的数据,可能导致脏读、不可重复读、幻读等问题,性能最好,但一般不使用。
  • 读已提交(Read Committed):一个事务只能读取另一个已提交的数据,可以避免脏读,但可能导致不可重复读、幻读等问题,是Oracle和SQL Server的默认隔离级别。
  • 可重复读(Repeatable Read):一个事务在执行过程中,对同一条数据多次查询结果都一致,可以避免脏读和不可重复读,但可能导致幻读问题,是MySQL和PostgreSQL的默认隔离级别。
  • 串行化(Serializable):一个事务在执行前加上表级共享锁,防止其他事务对表进行任何修改操作,可以避免脏读、不可重复读和幻读等问题,但性能最差,一般不使用。
脏读不可重复读幻读
Read uncommitted
Read committed×
Repeatable read××
Serializable××

可以通过命令 set transaction 命令设置事务隔离级别: 

  1. set transaction isolation level 设置事务隔离级别
  2. select @@tx_isolation 查询当前事务隔离级别

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/610584
推荐阅读
相关标签
  

闽ICP备14008679号