一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結

程序員uauc 2021-09-20 00:48:32 阅读数:434

一篇 篇文章 文章 innodb- innodb

這些問題的根本其實又回到了事務的四大特性,不得不說 Theo Haerder 在 1983 年就能抽象出來如此高度凝練的總結實在是讓當下汗顏。

下面我就從 InnoDB 如何保證四大特性入手,逐一分析事務機制的實現。

保證原子性的關鍵技術 - undo log

對於事務的原子性來說,該事務內所有操作要麼全部成功要麼全部失敗就是事務的原子性。

全部成功這個毋庸置疑,如果中間突然失敗,原子性該如何保證呢?是否該回滾當前已經執行成功的操作。

InnoDB 提供了一種日志:undo log,它有兩個作用:提供 回滾 和 多個行版本控制(MVCC)。

比如一條 delete 操作在 undo log 中會對應一條 insert 記錄,反之亦然。當 update 操作時,它會記錄一條相反的 update 記錄。

當執行 rollback 時,就可以從 undo log 中的邏輯記錄讀取到相應的內容並進行回滾。

有時候應用到行版本控制的時候,也是通過 undo log 來實現的:當讀取的某一行被其他事務鎖定時,它可以從 undo log 中分析出該行記錄以前的數據是什麼,從而提供該行版本信息,讓用戶實現非鎖定一致性讀取。

undo log 的存儲方式

InnoDB 存儲引擎對 undo log 的管理采用段的方式。rollback segment 稱為回滾段,每個回滾段中有 1024 個 undo log slot 。

在以前老版本,只支持 1 個 rollback segment,這樣就只能記錄 1024 個 undo log slot。後來 MySQL5.5 可以支持 128 個 rollback slot,即支持 128 * 1024 個 undo log 操作。

MySQL5.6 之前,undo log 錶空間比特於共享錶空間的回滾段中,共享錶空間的默認的名稱是 ibdata,比特於數據文件目錄中。
MySQL5.6 之後,undo log 錶空間可以配置成獨立的文件,但是提前需要在配置文件中配置,完成數據庫初始化後生效且不可改變 undo log 文件的個數。如果初始化數據庫之前沒有進行相關配置,那麼就無法配置成獨立的錶空間了。
MySQL5.7 之後的獨立 undo log 錶空間配置參數如下:

innodb_undo_directory = /data/undospace/ #undo獨立錶空間的存放目錄
innodb_undo_logs = 128 #回滾段為128KB
innodb_undo_tablespaces = 4 #指定有4個undo log文件

  • 1.
  • 2.
  • 3.

undo log 的删除時機

undo log 文件的個數是有限制的,所以不用無限堆積日志文件。undo log 記錄的是當前事務操作的反向記錄,理論上當前事務結束,undo log 日志就可以廢弃。上面也提到過的多版本並發控制機制在隔離級別為?repeatable read?的時候事務讀取的數據都是該事務最新提交的版本,那麼只要該事務不結束,行版本記錄就不能删除。

另外不同的 sql 語句對應的 undo log 類型也不一樣,比如:

  • insert 語句:因為 insert 操作本身只對該事務可見,事務提交之前別的連接是看不到的,所以 insert 操作產生的 undo log 日志在事務提交之後會馬上直接删除,後續不會再被別的功能使用。
  • update / delete 語句:delete 操作在事務中並不會真的先删除數據,而是將該條數據打上 “delete_bit” 標識,後續的删除操作是在事務提交後的 purge 線程獨立操作。這兩種操作產生的 undo log 日志都可以用反向的 update 來代替,這種操作上面說過 MVCC 機制可能會用上,所以就不能在事務結束之後直接删除。

在事務提交之後,也不是馬上就删除該事務對應的 undo log 日志,而是將該事務對應的文件塊放入到删除列錶中,未來通過 purge 來删除。並且提交事務時,還會判斷 undo log 分配的頁是否可以重用,如果可以重用,則會分配給後面來的事務,避免為每個獨立的事務分配獨立的 undo log 頁而浪費存儲空間和性能。

持久性 - redo log

redo log 即重做日志,重做日志記錄每次操作的物理修改。

說 redo log 之前其實是要先說一下 binlog,不然就不知道為什麼要引入 redo log。

bin log = binary log,二進制日志,它記錄了除了 select 之外所有的 DDL 和 DML 語句。以事件形式記錄,還包含語句所執行的消耗的時間,MySQL 的二進制日志是事務安全型的。

binlog日志有兩個最重要的使用場景:

  1. mysql 主從複制: mysql replication 在 master 端開啟 binlog,master 把它的二進制日志傳遞給 slaves 來達到 master-slave 數據一致的目的。
  2. 數據恢複: 通過 mysqlbinlog 工具來恢複數據。

binlog 日志包括兩類文件:

  1. 二進制日志索引文件(文件名後綴為 .index)用於記錄所有的二進制文件。
  2. 二進制日志文件(文件名後綴為 .00000*)記錄數據庫所有的 DDL 和 DML 語句事件。

binlog 文件是通過追加的方式寫入的,可通過配置參數max_binlog_size設置每個 binlog 文件的大小,當文件大小大於給定值後,日志會發生滾動,之後的日志記錄到新的文件上。
binlog 有兩種記錄模式,statement 格式的話是記 sql 語句,row 格式會記錄行的內容。

持久性問題一般在發生故障的情况才會重視。在啟動 MySQL 之後無論上次是否正常關閉都會進行恢複操作,我們假設現在沒有 redo log 只有 binlog,那麼數據文件的更新和寫入 binlog 只有兩種情况:

  • 先更新數據文件,再寫入 binlog;
  • 先寫入 binlog,再更新數據文件。

如果先更新數據文件,接著服務器宕機,則導致 binlog 中缺少最後的更新信息;如果先寫 binlog 再更新數據則可能導致數據文件未被更新。

所以在只有 binlog 的環境中 MySQL 是不具備 crash-safe 的能力。另外一開始的 MySQL 使用 MyISAM 引擎,它只有 binlog,所以自然不支持事務。後面引入了 InnoDB 之後才開始使用另外一套日志系統- redo log 來實現 crash-safe 功能。

redo log 和 binlog 的區別:

  • redo log 是 InnoDB 引擎特有的,binlog 是MySQL server 層實現的功能,與引擎無關。
  • redo log 是物理日志,記錄 “在某個數據頁做了什麼修改”;binlog 是邏輯日志,記錄 sql 語句的原始邏輯,比如 “給 ID = 1 這一行的 name value set ‘xiaoming’ ”。
  • redo log 空間是固定的,用完之後會覆蓋之前的數據;binlog 是追加寫,當前文件寫完之後會開啟一個新文件繼續寫。

redo log 由兩部分組成:

  • 內存中的重做日志緩沖(redo log buffer)
  • 重做日志文件(redo log file)

一個更新事務的整體流程

一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_Java

從一個事務的更新過程出發看看一個事務更新過程中 redo log 處於什麼地比特。

  1. 首先檢查 Buffer cache 中是否存在這條數據,如果存在直接返回,如果不存在則去索引樹中讀取這條數據並加載到 Buffer Cache。
  2. 執行器拿到這條行數據之後對它執行相應的更新操作。
  3. 將這條待更新的行數據調用執行引擎更新到 Buffer Cache 中,同時將這個記錄更新到 redo log 裏面,redo log 包含兩個部分的更新,更新完畢,此時 redo log 處於 prepare 的狀態,然後告訴執行器,你可以提交事務。
  4. 執行器生成這個操作的 binlog 日志,並把 binlog 寫入磁盤。
  5. 執行器調用引擎的提交事務接口,引擎把剛寫入的 redo log 改為 commit 狀態,整個事務提交完成。

這裏我們注意到在 redo log 的提交過程中引入了兩階段提交

兩階段提交

為什麼必須有 “兩階段提交” 呢?這是為了讓兩份日志之間的邏輯一致。

前面我們說過了,binlog 會記錄所有的邏輯操作,並且是采用 “追加寫” 的形式。如果你的 DBA 承諾說半個月內可以恢複,那麼備份系統中一定會保存最近半個月的所有binlog,同時系統會定期做整庫備份。

由於 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交,要麼就是先寫完 redo log 再寫 binlog,或者采用反過來的順序,我們看看這兩種方式會有什麼問題,用上面的 update 示例做假設:

  1. 先寫 redo log 後寫 binlog。假設在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程异常重啟。因為 redo log 已經寫完,系統即使崩潰仍然能够把數據恢複回來。但是 binlog 裏面就沒有記錄這個語句,因此備份日志的時候 binlog 裏面就沒有這條語句。

    但是如果需要用這個 binlog 來恢複臨時庫的話,由於這個語句的 binlog 丟失,恢複出來的值就與原庫值不同。

  2. 先寫 binlog 後寫 redo log。如果在 binlog 寫完之後宕機,由於 redo log 還沒寫,崩潰恢複以後這個事務無效,所以這一行的值還是未更新以前的值。但是 binlog 裏面已經記錄了崩潰前的更新記錄, binlog 來恢複的時候就多了一個事務出來與原庫的值不同。

可以看到,兩階段提交就是為了防止 binlog 和 redo log 不一致發生。同時我們也注意到為了這個崩潰恢複的一致性問題引入了很多新的東西,也讓系統複雜了很多,所以有得有失。

InnoDB通過?Force Log at Commit?機制保證持久性:當事務提交(COMMIT)時,必須先將該事務的所有日志緩沖寫入到重做日志文件進行持久化,才能 COMMIT 成功。

為了確保每次日志都寫入 redo log 文件,在每次將 redo log buffer cache 寫入重做日志文件後,InnoDB 引擎都需要調用一次 fsync 操作。因此磁盤的性能决定了事務提交的性能,也就是數據庫的性能。

innodb_flush_log_at_trx_commit?參數控制重做日志刷新到磁盤的策略:

  • 0:事務提交時不進行寫入重做日志操作,僅在 master thread 每秒進行一次。
  • 1:事務提交時必須調用一次fsync操作。
  • 2:僅寫入文件系統緩存,不進行fsync操作。

log buffer 根據如下規則寫入到磁盤重做日志文件中:

  • 事務提交時。
  • 當 log buffer 中有一半的內存空間已經被使用。
  • log checkpoint 時,checkpoint在一定程度上代錶了刷到磁盤時日志所處的LSN比特置。

一致性 和 隔離性實現 - 鎖機制 和 MVCC

實現一致性和隔離性是保證數據准確性的關鍵一環,前面兩個特性保證數據恢複不出問題,這兩個特性要保證數據插入和讀取不出問題。實現一致性和隔離性主要使用了兩個機制:

  • 鎖機制
  • 多版本並發控制

下面我們就事務會產生哪些問題,MySQL 提出什麼方式來解决問題,這些方式的實現方案又是什麼來講解。

並發下事務會產生哪些問題

事務 A 和 事務 B 同時操作一個資源,根據不同的情况可能會出現不同問題,總結下來有以下幾種:

  • 髒讀

    事務 A 讀到了事務 B 還未提交的數據。

  • 幻讀

    在當前事務中發現了不屬於當前事務操作的數據。幻讀是針對數據 insert 操作來說的。假設事務A對某些行的內容作了更改,但是還未提交,此時事務 B 插入了與事務 A 更改前的記錄相同的記錄行,並且在事務 A 提交之前先提交了,而這時,在事務A中查詢,會發現好像剛剛的更改對於某些數據未起作用,但其實是事務 B 剛插入進來的,讓用戶感覺出現了幻覺,這就叫幻讀。

  • 可重複讀

    可重複讀指的是在一個事務內,最開始讀到的數據和事務結束前的任意時刻讀到的同一批數據都是一致的。通常針對數據 update 操作。

  • 不可重複讀

    在同一個事務中兩次讀取一個數據的結果不一樣。對比可重複讀,不可重複讀指的是在同一事務內,不同的時刻讀到的同一批數據可能是不一樣的,可能會受到其他事務的影響,比如其他事務改了這批數據並提交了。

為什麼會提出隔離級別的概念

為了解决事務並發過程中可能會產生的這些問題,SQL 標准定義的四種隔離級別被 ANSI(美國國家標准學會)和 ISO/IEC(國際標准)采用,每種級別對事務的處理能力會有不同程度的影響。

SQL 標准定義了四種隔離級別,MySQL 全都支持。這四種隔離級別分別是:

  1. 讀未提交(READ UNCOMMITTED)
  2. 讀提交 (READ COMMITTED)
  3. 可重複讀 (REPEATABLE READ)
  4. 串行化 (SERIALIZABLE)

從上往下,隔離强度逐漸增强,性能逐漸變差。采用哪種隔離級別要根據系統需求權衡决定,其中,可重複讀是 MySQL 的默認級別。

事務隔離其實就是為了解决上面提到的髒讀、不可重複讀、幻讀這幾個問題,下面展示了 4 種隔離級別對這三個問題的解决程度。

隔離級別 髒讀 不可重複讀 幻讀
讀未提交 會發生 會發生 會發生
讀提交 不會發生 會發生 會發生
可重複讀 不會發生 不會發生 會發生
串行化 不會發生 不會發生 不會發生

只有串行化的隔離級別解决了全部這 3 個問題,其他的 3 個隔離級別都有缺陷。

如何設置事務隔離級別

我們可以通過以下語句查看當前數據庫的隔離級別,通過下面語句可以看出我使用的 MySQL 的隔離級別是?REPEATABLE-READ,也就是可重複讀,這也是 MySQL 的默認級別。

mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.02 sec)
或者:
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

當然我們也能手動修改事務的隔離級別:

set [作用域] transaction isolation level [事務隔離級別];
作用域包含:
SESSION:SESSION 只針對當前回話窗口
GLOBAL:全局生效
隔離級別包含:
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

我們來測試一下各個隔離級別對事務的影響。

新建錶:

CREATE TABLE `test_db` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
PRIMARY KEY (`id`),
KEY `name_idx` (`name`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='測試錶';

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

插入一些測試數據。

讀未提交(READ UNCOMMITTED)

首先設置事務隔離級別:

set global transaction isolation level READ UNCOMMITTED;

  • 1.

注意:設置完全局隔離級別只對新打開的 session 有效,曆史打開的是不會受到影響的。

首先關閉事務自動提交:

set autocommit = 0;

  • 1.

開啟事務 A:

Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> insert test_db (name) values ('xiaocee');
Query OK, 1 row affected (0.01 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

在事務A 中插入一條數據,並未提交事務。

接著開啟事務B:

mysql> select * from test_db;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaocee |
+----+-----------+
9 rows in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

事務 B 中能够查到這條數據。即不同的事務能讀到對方未提交的數據。連髒讀都無法解决,可重複讀和幻讀更沒法解决。

讀已提交

讀已提交的數據肯定能解决髒讀問題,但是對於幻讀和不可重複讀無法將解决。

首先設置事務隔離級別:

set global transaction isolation level READ COMMITTED;

  • 1.

現在數據庫數據如下:

mysql> select * from test_db;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming2 |
| 2 | xiaohong |
| 3 | xiaowei |
| 4 | xiaowei1 |
| 5 | xiaoli |
| 6 | xiaoche |
| 8 | xiaoche |
| 10 | xiaoche |
| 12 | xiaocee |
+----+-----------+
9 rows in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

開啟事務 A 將?id=1?的數據改為 “xiaoming3”:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update test_db set name = 'xiaoming3' where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

這裏事務 A 未提交,接著開啟事務B 做第一次查詢:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_db where id = 1;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming2 |
+----+-----------+
9 rows in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

事務B查詢還是原始值。

下面提交事務 A:

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

  • 1.
  • 2.

接著在事務 B 中再查詢一次:

mysql> select * from test_db where id = 1;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming3 |
+----+-----------+
1 row in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

當然這次查到的肯定是人家已提交的數據。這裏發生的問題就是不可重複讀:即同一個事務內每次讀取同一條數據的結果不一樣。

可重複讀

可重複讀隔離級別的含義就是重讀每次都一樣不會有問題。這就意味著一個事務不會讀取到別的事務未提交的修改。但是這裏就會有另一個問題:在別的事務提交之前它讀到的數據不會發生變化,那麼另一個事務如果將結果 a 改為 b,接著又改為了 a,對於當前事務來說直到另一個事務提交之後它再讀才會獲取到最新結果,但是它並不知道這期間別的事務對數據做了更新,這就是幻讀的問題

首先設置事務隔離級別:

set global transaction isolation level REPEATABLE READ;

  • 1.

現在數據庫數據如下:

mysql> select * from test_db;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming3 |
| 2 | xiaohong |
| 3 | xiaowei |
| 4 | xiaowei1 |
| 5 | xiaoli |
| 6 | xiaoche |
| 8 | xiaoche |
| 10 | xiaoche |
| 12 | xiaocee |
+----+-----------+
9 rows in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

開啟事務 A 將?id=1?的數據改為 “xiaoming4”:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update test_db set name = 'xiaoming3' where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

這裏事務 A 未提交,接著開啟事務B 做第一次查詢:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_db where id = 1;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming3 |
+----+-----------+
9 rows in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

事務B查詢還是原始值。

下面提交事務 A:

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

  • 1.
  • 2.

接著在事務 B 中再查詢一次:

mysql> select * from test_db where id = 1;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming3 |
+----+-----------+
1 row in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

查詢到還是一樣的結果,下面提交事務B ,然後再查詢:

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_db where id = 1;
+----+-----------+
| id | name |
+----+-----------+
| 1 | xiaoming4 |
+----+-----------+
1 row in set (0.00 sec)

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

提交完之後再查就是 “xiaoming4”。

這也意味著在事務B未提交期間,事務A做任何操作對B來說都是盲視的。

串行化讀

串行化讀意味著將所有事務變為順序執行,所以就不存在上述的四種問題,當然這也意味著效率是最低的。

有了隔離級別的概念,那隔離級別又是怎麼實現的呢?我們接下來要講的鎖機制就是實現隔離級別的重要手段。

鎖的類型

從鎖定資源的角度看, MySQL 中的鎖分類:

  • 錶級鎖
  • 行級鎖
  • 頁面鎖

錶級鎖?的特點是每次都整張錶加鎖,加鎖速度快,但是鎖的粒度太大,並發性就低,發生鎖沖突的概率大。

錶鎖的種類主要包含兩種:

  • 讀鎖 (共享鎖):同一份數據多個讀操作同時進行不會互相影響,但是讀操作會阻塞寫操作。
  • 寫鎖(排他鎖):當前寫操作沒有完成之前會阻塞其他讀和寫操作。

行級鎖?的特點是對一行數據加鎖,加鎖的開銷會大但是鎖粒度小發生鎖沖突的概率就低並發度提高了。

行鎖的種類包含:

  • 讀鎖(S 共享鎖):允許一個事務讀取某一行,其他事務在讀取期間無法修改該行數據但可以讀。
  • 寫鎖(X 排他鎖):允許當前獲得排它鎖的事務操作數據,其他事務在操作期間無法更改或者讀取。
  • 意向排它鎖(IX):一個事務給該數據行加排它鎖之前,必須先獲得 IX 鎖。
  • 意向共享鎖(IS):一個事務給該數據行加共享鎖之前必須先獲得 IS 鎖。

頁面鎖?因為MySQL 數據文件存儲是按照頁去劃分的,所以這個鎖是 MySQL 特有的。開銷和加鎖時間界於錶鎖和行鎖之間,鎖定粒度界於錶鎖和行鎖之間,並發度一般。

在 InnoDB 引擎中默認使用行級鎖,我們重點就行級鎖的加鎖、解鎖來做一些說明。

行級鎖上鎖分為 隱式上鎖 和 顯式上鎖。

隱式上鎖是默認的上鎖方式,select不會自動上鎖,insertupdatedelete?都會自動加排它鎖。在語句執行完畢會釋放。

顯式上鎖即通過手動的方式給 sql 語句加鎖,比如:

共享鎖:

select * from tableName lock in share mode;

  • 1.

排他鎖:

select * from tableName for update;

  • 1.

最後

畢竟工作也這麼久了 ,除了途虎一輪,也七七八八面試了不少大廠,像阿裏、餓了麼、美團、滴滴這些面試過程就不一一寫在這篇文章上了。我會整理一份詳細的面試過程及大家想知道的一些問題細節

美團面試經驗

一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_Java_02
字節面試經驗
一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_後端_03
菜鳥面試經驗
一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_Java_04
螞蟻金服面試經驗
一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_程序員_05
唯品會面試經驗
一篇文章把-InnoDB-的事務機制給你弄的明明白白,MySQL萬字精華總結_Java_06

因篇幅有限,圖文無法詳細發出

 CodeChina開源項目:【一線大廠Java面試題解析+核心總結學習筆記+最新講解視頻】

版权声明:本文为[程序員uauc]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210920004831706U.html