關於 MySQL 四種事務隔離級別的設置與簡要說明

1 摘要

關於 MySQL 的事務隔離級別,是面試中經常問到的問題(雖然大多數項目中都用不到),作者在這裏整理了一些筆記,僅供參考。

2 MySQL 事務隔離級別劃分

數據庫事務隔離級別分爲四個等級,分別爲:未提交讀( read-uncommitted)、提交讀(read committed)、可重複讀(repeatable read)、串行序列化(serializable)

事務等級與可能出現的異常:

事務等級 髒讀 不可重複讀 幻讀
read uncommitted
read committed X
repeatable read X X
serializable X X X

從上到下,事務的隔離級別逐漸增高;事務的隔離級別越高,程序執行效率越低。

MySQL 的默認事物隔離級別爲 repeadable read

名詞解釋:

髒讀: 在一個事務中讀取到了另一個未提交事務的數據,即爲髒讀

不可重讀讀: 在一個事務處理過程中,另一個事物插入進來,並更新了數據,原先的事務前後兩次相同的語句查詢,結果不一樣,即爲不可重複讀

幻讀: 在一個事物處理過程中,另一個事務插入進來,並更新了數據,此時原來的事務也插入數據,則實際上會受到後一個事務數據的影響,即爲幻讀。

3 MySQL 事務隔離級別的查詢與設置

-- 查詢 mysql 版本
SELECT VERSION();

-- 查詢 mysql 事務隔離級別(5.7 及以下)
SELECT @@tx_isolation;

-- 查詢 mysql 事務隔離級別(5.7+)
SELECT @@transaction_isolation;

設置 MySQL 事務隔離級別:

SET {SESSION | GLOBAL} TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};

SESSION: 爲當前會話

GLOBAL: 爲本次連接所有會話

示例:

-- 設置當前會話的事務隔離級別爲提交讀(`READ COMMITTED`)  
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

無論是 SESSION 還是 GLOBAL 級別,斷開 MySQL 連接,下次再連接的時候,還是原來的事務隔離級別,因此徹底更新MySQL 事務隔離級別,需要修改 MySQL 配置文件

Linux 系統下 MySQL 配置文件位置爲: /etc/my.conf

[mysqld] 標籤下配置(在其他標籤下設置無效,如果沒有該標籤則手動添加):

[mysqld]
transaction-isolation = READ-COMMITTED

在配置文件中設置的事務隔離級別爲: READ-UNCOMMITTEDREAD-COMMITTEDREPEATABLE-READSERIALEZABLE

配置文件修改之後需要重啓 MySQL 服務

4 事務隔離演示準備

4.1 數據準備

數據庫表

-- 用戶信息表
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id 主鍵',
  `user_name` varchar(30) DEFAULT '' COMMENT '用戶名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶信息表';

創建測試數據

-- 批量插入用戶信息
INSERT INTO `user_info`(`user_name`) VALUES ('張三'),
    ('李四'),
		('王五');    

4.2 MySQL 事務開啓與回滾命令

-- 開啓事務
start transaction;
-- do sometring

-- 提交事務
commit;
-- 開啓事務
start transaction;
-- do sometring

-- 回滾事務
rollback;

5 不同事務隔離級別下異常演示

5.1 事務隔離級別爲未提交讀(read uncommitted)

會話1 會話2
設置當前會話事務隔離級別:
set session transaction isolation level read uncommitted;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
READ-UNCOMMITTED
設置當前會話事務隔離級別:
set session transaction isolation level read uncommitted;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
READ-UNCOMMITTED
開啓事務:
start transaction;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:
開啓事務:
start transaction;
向用戶信息表插入一條數據:
insert into user_info(user_name) values('read Uncommitted1');
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:

此時,會話1中查出了會話2未提交的數據,即爲髒讀

5.2 事務隔離級別爲提交讀(read committed)

會話1 會話2
設置當前會話事務隔離級別:
set session transaction isolation level read committed;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
READ-COMMITTED
設置當前會話事務隔離級別:
set session transaction isolation level read committed;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
READ-COMMITTED
開啓事務:
start transaction;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:
開啓事務:
start transaction;
向用戶信息表插入一條數據:
insert into user_info(user_name) values('read Committed1');
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:

此時會話1中沒有查詢到會話2中未提交的數據,即沒有出現髒讀。

繼續試驗

會話1 會話2
提交事務:
commit;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:

此時,會話1中查詢到了會話2中提交的數據,會話1同一個事務中前後進行了兩次相同的查詢,但是結果卻不一致,即爲不可重複讀。

5.3 事務隔離級別爲可重複讀(repeatable read)

會話1 會話2
設置當前會話事務隔離級別:
set session transaction isolation level repeatable read;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
REPEATABLE-READ
設置當前會話事務隔離級別:
set session transaction isolation level repeatable read;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
REPEATABLE-READ
開啓事務:
start transaction;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:
開啓事務:
start transaction;
向用戶信息表插入一條數據:
insert into user_info(id,user_name) values(6,'repeatable read1');
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:

此時會話1中沒有查詢到會話2中未提交的數據,即沒有出現髒讀。

繼續試驗

會話1 會話2
提交事務:
commit;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:

此時,會話1中還是沒有查詢到會話2的事務提交的數據,即沒有出現不可重複讀的問題。

繼續試驗

會話1 會話2
向用戶信息表插入一條數據:
insert into user_info(id,user_name) values(6,'repeatable read1');
提示錯誤:
ERROR 1062 (23000): Duplicate entry '6' for key 'PRIMARY'

會話1中沒有查詢到 id6 的數據,但是插入一條 id6 的數據,卻提示插入失敗,是因爲在會話 2 中已經插入了一條 id6 的數據,這種現象即爲幻讀。

repeadable read 事務隔離級別雖然解決了可重複讀的問題,但是其他事務已經插入的數據仍然會對當前事務造成影響,即會出現幻讀問題。

5.4 事務隔離級別爲序列化(serializable)

會話1 會話2
設置當前會話事務隔離級別:
set session transaction isolation level serializable;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
SERIALIZABLE
設置當前會話事務隔離級別:
set session transaction isolation level serializable;
查詢當前會話事務隔離級別:
select @@tx_isolation;
結果爲:
SERIALIZABLE
開啓事務:
start transaction;
查詢用戶信息表所有數據:
select * from user_info;
查詢結果爲:
開啓事務:
start transaction;
向用戶信息表插入一條數據:
insert into user_info(id,user_name) values(7,'seiralizable1');
結果爲:
線程阻塞,等待提交
等待一段時間後提示:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
提交事務:
commit;
向用戶信息表插入一條數據:
insert into user_info(id,user_name) values(7,'seiralizable1');
提示插入成功
提交事務:
commit;
也提示成功

從示例中可以看出,當數據庫事務隔離級別設置爲序列化(serializable)時,可以同時開啓多個事務,但是每次只能由一個事務進行提交,而且事務提交順序遵循先進先出原則(誰先開啓的事務,誰就能先提交,後開啓的時候必須等待前邊的事務提交之後才能提交),如果後開啓的事務等待超時,會導致提交失敗,並重新開啓事務

6 參考資料推薦

MySQL 四種事務隔離級的說明

MySQL的rollback–事務回滾

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章