ホームページ  >  記事  >  データベース  >  MySQLトランザクションの詳細な分析

MySQLトランザクションの詳細な分析

WBOY
WBOY転載
2022-03-10 17:59:111545ブラウズ

この記事では、mysql に関する関連知識を提供します。主に、MySQL のトランザクション ACID 特性と MySQL トランザクション制御プロセスの構文を紹介し、同時トランザクション処理状況で発生する可能性のある例外を紹介します。ダーティ リード、ファントム リード、非反復読み取りなどについて説明し、最後にトランザクション分離レベルを紹介します。皆さんのお役に立てれば幸いです。

MySQLトランザクションの詳細な分析

推奨学習: mysql 学習チュートリアル

実際のビジネス シナリオでは、運用の整合性をどのように確保するかが重要な問題になります。論理的に関連性の高い一連の操作を順番に実行するため、途中でエラーが発生すると、データの混乱が生じる可能性があります。

ATM からお金を引き出すシーンを想像してください。1000 元を引き出すとき、ATM は 10 回ごとに 100 元を吐き出すのではなく、数え終わった後に一度に 1000 元を吐き出します。 . これは、操作の完全性を確保するため、途中での機械の故障によるデータの不整合が発生することなく、千元が完全に引き出されて残高が差し引かれるか、一銭も引き落とされず残高が変更されないままになります。このような完全な操作は transaction と呼ばれます。トランザクション内のすべての操作は、正常に実行されるか、まったく実行されないかのどちらかです。

この記事では、MySQL トランザクション ACID の機能と MySQL トランザクション制御プロセスの構文を紹介し、ダーティ リード、ファントム リード、非トランザクションなどの同時トランザクション処理で発生する可能性のある例外を紹介します。 -repeatable Read などを行って、最後にトランザクション分離レベルを導入します。

トランザクション分離を実現するためのロックやMVCCについては、次回以降の記事で紹介していきます。

ACID 機能

トランザクション処理は、バッチで実行する必要がある MySQL 操作の管理メカニズムです。トランザクション プロセス中、操作のバッチ全体が正しく実行されない限り、トランザクション内の操作はすべて無効になります。中央は、エラーが発生した場合、システム データに誤った変更が生じないように、元の安全な状態に ロールバックします。

前回の記事で、MySQL 5.5 以降、デフォルトのストレージ エンジンが MyISAM から InnoDB に置き換えられたと述べました。その重要な理由の 1 つは、InnoDB がトランザクションをサポートしているためです。私たちは SHOW ENGINES MySQL のさまざまなストレージ エンジンの説明を見てみましょう。
MySQLトランザクションの詳細な分析
トランザクションの 4 つの最も重要な特性は、よく ACID 特性
A - アトミック Atomicity と呼ばれます。トランザクションは分割できない最小単位です。トランザクション内のすべての操作は成功するか失敗するかのどちらかであり、中間状態はありません。アトミック性は主にトランザクション ログの ロールバック ログ (アンドゥ ログ) によって実現され、トランザクションがデータベースを変更すると、InnoDB は挿入操作などの操作に基づいて反対の操作のアンドゥ ログを生成します。を実行すると削除レコードが生成され、トランザクションの実行が失敗した場合やロールバックが呼び出された場合には、アンドゥログの内容に基づいて実行前の状態に戻ります。

C - 整合性 Consistency: トランザクション実行前後でデータは法的整合性を保っており、例外が発生してもデータベースの整合性が例外によって破壊されることはありません。一意性制約などの制約。

I - 分離 Isolation: 各トランザクションは互いに独立しており、他のトランザクションの実行の影響を受けません。トランザクションは、コミットするまで他のトランザクションには表示されません。分離はトランザクションの分離レベルによって定義され、書き込み操作の分離を保証するためにロック メカニズムが使用され、読み取り操作の分離を保証するために MVCC が使用されます。これについては後で詳しく説明します。

D - 耐久性永続性 : トランザクションがコミットされた後のデータへの変更は永続的であり、データベースがダウンした場合でも失われません。トランザクション ログ (REDO ログ) を確認します。トランザクションが変更される前に変更情報がREDOログに書き込まれており、データベースがダウンした場合、リカバリ後にREDOログのレコードを読み取ってデータを復元します。

トランザクション制御構文

MySQL トランザクション制御には、トランザクションの開始、コミット、ロールバック、セーブ ポイントという重要なノードがいくつかあります。

トランザクションを開くとは、トランザクションが実行を開始することを意味します。ステートメントは、

START TRANSACTION または BEGIN です。トランザクションを送信すると、トランザクション内のすべての更新が物理データベースに書き込まれることになります。ディスク上のトランザクション 通常の実行が終了した場合、ステートメントは COMMIT ですが、例外が発生してロールバックが必要な場合、ステートメントは ROLLBACK となります。なお、一度コミットしたトランザクションはロールバックできないため、コード実行中に例外をキャッチした場合はコミットせずに直接ロールバックを実行する必要があります。

たとえば、A が B に 100 元を送金します:

// 正常执行,提交
BEGIN; # 开启事务
UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A';
UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B';
COMMIT; # 提交事务

// 发生异常,回滚
BEGIN; # 开启事务
UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A';
UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B';
ROLLBACK; # 事务回滚
複雑なシナリオでは、操作全体をロールバックする必要がない場合がありますが、操作をバッチで実行し、特定の時点までロールバックします。これは、大きなトランザクションの下に複数のサブトランザクションをネストするのと同じであり、MySQL では、リテンション ポイント

SAVEPOINT を使用してこれを実現できます。

BEGIN;
insert into user_tbl (id) values (1) ;
insert into user_tbl (id) values (2) ;
ROLLBACK;   # 1,2 都没有写入
BEGIN;
insert into user_tbl (id) values (1) ;
SAVEPOINT s1;
insert into user_tbl (id) values (2) ;
ROLLBACK TO s1;   # 回滚到保留点 s1, 因此 1 成功写入,2 被回滚, 最终结果为 1
RELEASE SAVEPOINT s1; # 释放保留点

顺便提一下,事务有隐式事务(自动提交)和显示事务(必须手动提交)两种,MySQL 默认为隐式事务,会进行自动提交,通过 autocommit 参数来控制。

# 查看变量
SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
# 开启自动提交(默认)
SET autocommit = 1;
# 关闭自动提交
SET autocommit = 0;

在自动提交状态下,如果没有显示的开启事务,那每一条语句都是一个事务,系统会自动对每一条 sql 执行 commit 操作。使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT 或 ROLLBACK 结束事务之后,自动提交模式会恢复到之前的状态。

关于事务还有另一个参数 completion_type,默认取值为 0 (NO_CHAIN)

# 查看变量
SHOW VARIABLES LIKE 'completion_type';
+-----------------+----------+
| Variable_name   |   Value  |
+-----------------+----------+
| completion_type | NO_CHAIN |
+-----------------+----------+

completion_type = 0: 默认值,执行 commit 后不会自动开启新的事务。
completion_type = 1: 执行 commit 时,相当于执行 COMMIT AND CHAIN,自动开启一个相同隔离级别的事务。
completion_type = 2: 执行 commit 时,相当于执行 COMMIT AND RELEASE,提交事务后自动断开服务器连接。

事务并发异常

在实际产线环境下,可能会存在大规模并发请求的情况,如果没有妥善的设置事务的隔离级别,就可能导致一些异常情况的出现,最常见的几种异常为脏读(Dirty Read)幻读(Phantom Read)不可重复读(Unrepeatable Read)

脏读

脏读指一个事务访问到了另一个事务未提交的数据,如下过程:

  1. 假设 a 的值为 1,事务 2 把 a 改为 2,此时事务还未提交
  2. 在这个时候,事务 1 读取 a,读得 a 的值为 2,事务 1 读取完成
  3. 结果事务 2 回滚了对 a 的修改(或者是未 commit),于是 a 的值变回 1
  4. 这就导致事实上 a 的值为 1,但是事务 1 取得的结果为 2,所以事务 1 读到了脏数据,发生脏读
    MySQLトランザクションの詳細な分析

不可重复读

不可重复读指一个事务多次读取同一数据的过程中,数据值 内容 发生了改变,导致没有办法读到相同的值,描述的是针对同一条数据 update/delete 的现象,如下过程:

  1. 事务 1 读取 a,此时 a = 1
  2. 此时事务 2 将 a 修改为 2,并成功提交,改动生效
  3. 事务 1 又一次读取 a,此时 a = 2
  4. 事务 1 在同一个事务里面两次读取同一个值,数据值内容却发生了改变,发生不可重复读
    MySQLトランザクションの詳細な分析

幻读

幻读指一个事务多次读取同一数据的过程中,全局数据(如数据行数)发生了改变,仿佛产生了幻觉,描述的是针对全表 insert/delete 的现象,如下过程:

  1. 事务 1 第一次读取数量,得到 10 条数据
  2. 此时事务 2 插入了一条数据并成功提交,改动生效,数据变成 11 条
  3. 事务 1 再次读取数量,得到 11 条数据,对事务 1 而言莫名其妙的多了一条,好像产生幻觉了一样,发生幻读
    MySQLトランザクションの詳細な分析

或者是另一种场景,比如对于有唯一性约束的字段(如 id),发生如下过程:

  1. 事务 1 要插入 id = 5 的记录,先查询数据库,发现不存在 id = 5 的数据,可以正常插入。
  2. 这时候事务 2 插入了一条数据 id = 5。
  3. 事务 1 插入 id = 5 时,发现报错唯一性冲突,对事务 1 来讲就好像见了鬼了,我刚刚明明检查过没有,怎么这时候又有了。
    MySQLトランザクションの詳細な分析

事务隔离级别

串行化的事务处理方式当然是最安全的,但是串行无法满足数据库高并发访问的需求,作为妥协,有时不得不降低数据库的隔离标准来换取事务的并发能力,通过在可控的范围内牺牲正确性来换取效率的提升,这种权衡通过事务的隔离级别来实现。

数据库有 4 种事务隔离级别,由低到高依次为 读未提交 Read Uncommitted读已提交 Read Committed可重复读 Repeatable Read串行化 Serializable

  1. 读未提交 Read Uncommitted
    允许读取未提交的内容,这种级别下的查询不会加锁,因此脏读、不可重复读、幻读都有可能发生。

  2. 读已提交 Read Committed
    只允许读取已提交的内容,这种级别下的查询不会发生脏读,因为脏数据属于未提交的数据,所以不会被读取,但是依然有可能发生不可重复读和幻读。

  3. 可重复读 Repeatable Read (MySQL 的默认隔离级别)
    使用行级锁来保证一个事务在相同查询条件下两次查询得到的数据结果一致,可以避免脏读和不可重复读,但是没有办法避免幻读。

    需要特殊注意的是,Innodb 在 Repeatable Read 下通过 MVCC 提供了稳定的视图,因此 Innodb 的 RR 隔离级别下是不会出现上述幻读异常中的第一个场景的,但第二个场景还是会出现。

  4. 串行化 Serializable
    使用表级锁来保证所有事务的串行化,可以防止所有的异常情况,但是牺牲了系统的并发性。

查看隔离级别的命令为

SHOW VARIABLES LIKE 'transaction_isolation';
# 或者
SELECT @@global.tx_isolation, @@tx_isolation;

第二种方式可以查看全局和当前会话的隔离级别。
MySQLトランザクションの詳細な分析
设置隔离级别的命令为

# 将当前会话的隔离级别设为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
# 将全局的隔离级别设为读未提交
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

结语

本文简单介绍了 MySQL 事务的语法和 ACID 特性,以及事务并发处理中可能出现的异常情况和为了防止这些异常而设计的事务隔离级别。有兴趣的朋友可以尝试在两个不同的 MySQL 客户端来模拟四种隔离级别下三种异常的发生情况,在之后的文章中,会继续深入探讨 MySQL 是如何实现隔离级别的。

推荐学习:mysql学习视频教程

以上がMySQLトランザクションの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。