詳細解析MySQL事務

WBOY
發布: 2022-03-10 17:59:11
轉載
1555 人瀏覽過

這篇文章為大家帶來了關於mysql的相關知識,其中主要介紹了MySQL的事務ACID特性和MySQL事務控制流程的語法,並介紹事務並發處理中可能出現的異常情況,例如髒讀、幻讀、不可重複讀等等,最後介紹事務隔離級別,希望對大家有幫助。

詳細解析MySQL事務

推薦學習:mysql學習教學

#在實際業務場景中,如何保證操作的完整性是一個重要的議題,依序執行一系列邏輯強關聯的操作,如果在中途發生了錯誤,就很有可能導致資料的錯亂。

設想一下在ATM 取錢的場景,當我們取出一千元的時候,ATM 會在清點完成後一次性吐出一千元,而不是分十次每次吐出一百元,這就是為了確保作業的完整性,要麼完整的取走一千元,扣除餘額,要麼一分錢都沒有取走,餘額不變,而不會出現中途機器故障導致資料不一致的情況。這樣的一次完整操作叫做事務 transaction,一個事務中的所有操作要麼全部成功執行,要麼完全不執行。

本文將會介紹MySQL 的事務ACID 功能和MySQL 事務控制流程的語法,並介紹事務並發處理中可能出現的異常情況,例如髒讀、幻讀、不可重複讀等等,最後介紹事務隔離等級。

關於實現事務隔離性的鎖定和 MVCC,將會在後面的文章中介紹。

ACID 特性

事務處理是一種必須整批執行的MySQL 作業的管理機制,在事務過程中,除非整批作業全部正確執行,否則中間的任何一個操作出錯,都會回滾(Rollback) 到最初的安全狀態以確保不會對系統資料造成錯誤的改變。

之前的文章中我們提到過,MySQL 5.5 之後,預設的儲存引擎從MyISAM 替換成了InnoDB,這其中的一個重要原因就是因為InnoDB 支援事務,我們用SHOW ENGINES 來看看MySQL 中對各種儲存引擎的描述。
詳細解析MySQL事務
交易最重要的四個特性通常被稱為ACID 特性
A - Atomicity 原子性: 一個交易是一個不可分割的最小單位,事務中的所有操作要么全部成功,要么全部失敗,沒有中間狀態。原子性主要是透過交易日誌中的回溯日誌(undo log)來實現的,當交易對資料庫進行修改時,InnoDB 會根據操作產生相反操作的undo log,比如說對insert 操作,會產生delete 記錄,如果交易執行失敗或呼叫了rollback,就會根據undo log 的內容恢復到執行之前的狀態。

C - Consistency 一致性: 交易執行之前和執行之後資料都是合法的一致性狀態,即使發生了異常,也不會因為異常引而破壞資料庫的完整性約束,例如唯一性約束等。

I - Isolation 隔離性: 每個事務是彼此獨立的,不會受到其他事務的執行影響,事務在提交之前對其他事務不可見。隔離性透過事務的隔離等級來定義,並用鎖定機制來確保寫入作業的隔離性,用 MVCC 來確保讀取操作的隔離性,將在下文詳細介紹。

D - Durability 持久性: 交易提交之後對資料的修改是持久性的,即使資料庫當機也不會遺失,透過交易日誌中的重做日誌(redo log)來保證。在事務修改之前​​,會先把變更資訊預寫到 redo log 中,如果資料庫當機,復原後會讀取 redo log 中的記錄來復原資料。

事務控制語法

MySQL 事務控制有幾個重要節點,分別是交易的開啟,提交,回溯和保存點。

開啟事務代表交易開始執行,語句為START TRANSACTIONBEGIN,提交事務代表將事務中的所有更新都寫到磁碟的實體資料庫,事務正常執行結束,語句為COMMIT,如果發生異常需要回滾,則語句為ROLLBACK。要注意的是,一旦事務已經提交,就不能回滾了,因此,在程式碼執行過程中捕獲到異常的時候需要直接執行 rollback 而不是 commit。

例如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中文網其他相關文章!

相關標籤:
來源:csdn.net
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!