實驗 - MySql的事務隔離級別

通過百度搜索:“MySql 事務隔離級別”,“InnoDB 事務隔離級別” 發現很多的文章“特點”如下:

  • 重點在於解釋:不可重複讀和幻讀的區別;
  • 大部分結論是:在repeatable read的隔離級別下,解決了不可重複讀的問題,但是存在幻讀問題。

正確的分析可以參考:Innodb中的事務隔離級別和鎖的關係

關於髒讀、幻讀、不可重複、丟失更新,可做如下實驗。強調僅限MySql環境,各類數據庫的結論推測到其他數據庫上是不太合適的。

實驗證明目的是證明:MySql InnoDB的Repeatable Read級別是解決了不可重複讀和幻讀的問題,並直觀感受髒讀、幻讀、不可重複問題

準備表和數據

CREATE DATABASE Test;
USE Test;

CREATE TABLE test(
    id INT AUTO_INCREMENT,
    name VARCHAR(100),
    sex CHAR(1),
    PRIMARY KEY (id),
    KEY (name,sex)
)ENGINE = INNODB;

INSERT test SELECT 1,'saillen','男';
INSERT test SELECT 2,'wenwen','女';
INSERT test SELECT 3,'jocker','男';

實驗SQL

在各個級別下的實驗SQL都是一樣的,不一樣的是設置事務級別的語句

#事務1
SELECT @@tx_isolation \G
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation \G
START TRANSACTION;
#更新操作:髒讀、不可重複讀
UPDATE test SET name = 'sai' WHERE id = 1;
#插入操作:幻讀
INSERT test SELECT 4,'hacker','男';
COMMIT;

#事務2
SELECT @@tx_isolation \G
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation \G
START TRANSACTION;
#在事務1開始前執行一遍
SELECT * FROM test;
#在事務1update和insert後執行一遍
SELECT * FROM test;
#在事務1 commit後執行一遍,出現不可重複和幻讀
SELECT * FROM test;

Read Uncommitted級別實驗

髒讀發生在Read UnCommitted級別下,表示讀取到了其他併發的未提交事務的結果

實驗結果

事務1的結果:

mysql> SELECT @@tx_isolation \G
*************************** 1. 行 ***************************
@@tx_isolation: REPEATABLE-READ
1 行於數據集 (0.01 秒)

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.02 秒)

mysql> select @@tx_isolation ;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 行於數據集 (0.03 秒)

mysql> start transaction;
Query OK, 0 rows affected (0.02 秒)

mysql> update test set name = 'sai' where id = 1;
Query OK, 1 rows affected (0.03 秒)

mysql> select * from test where id = 1;
+----+------+------+
| id | name | sex  |
+----+------+------+
| 1  | sai  | 男  |
+----+------+------+
1 行於數據集 (0.02 秒)

mysql> rollback;
Query OK, 0 rows affected (0.02 秒)

mysql> select * from test where id = 1;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 1  | saillen | 男  |
+----+---------+------+
1 行於數據集 (0.03 秒)

事務2的結果:

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 行於數據集 (0.03 秒)

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.01 秒)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 行於數據集 (0.02 秒)

mysql> start transaction;
Query OK, 0 rows affected (0.02 秒)

mysql> select * from test where id = 1;
+----+------+------+
| id | name | sex  |
+----+------+------+
| 1  | sai  | 男  |
+----+------+------+
1 行於數據集 (0.02 秒)

mysql> select * from test where id = 1;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 1  | saillen | 男  |
+----+---------+------+
1 行於數據集 (0.04 秒)

mysql> commit;
Query OK, 0 rows affected (0.01 秒)

結論

MySql在READ UNCOMMITTED事務隔離級別下會有髒讀現象,因爲不加任何鎖,極端情況下才使用的級別;

Read Committed事務級別

不可重複讀是指事務1在事務過程中,事務2修改了事務1讀取的行,導致事務1兩次讀取到的結果不一致,重點區別是事務2 commit後才能感知到這次修改。這種情況某些時候是允許並且歡迎的,比如我們對賬戶做修改的時候,應該實時感知到餘額的變化,但是某些時候是不應該被感知的,比如做某個時間段的銷量統計的,庫存減少在事務中被感知,一是程序容易計算錯誤,二是我們計算的是某個時候的,下一時刻的變化不在考慮範圍內。

實驗結果

事務1的實驗結果

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 秒)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 行於數據集 (0.03 秒)

mysql> start transaction;
Query OK, 0 rows affected (0.02 秒)

mysql> update test set name = 'sai' where id = 1;
Query OK, 1 rows affected (0.02 秒)

mysql> insert test select 4,'hacker','男';
Query OK, 1 rows affected (0.01 秒)

mysql> commit;
Query OK, 0 rows affected (0.01 秒)

事務2實驗結果

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 秒)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 行於數據集 (0.03 秒)

mysql> start transaction;
Query OK, 0 rows affected (0.01 秒)

mysql> select * from test;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
3 行於數據集 (0.02 秒)

mysql> select * from test;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
3 行於數據集 (0.03 秒)

mysql> select * from test;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
3 行於數據集 (0.02 秒)

mysql> select * from test;
+----+--------+------+
| id | name   | sex  |
+----+--------+------+
| 4  | hacker | 男  |
| 3  | jocker | 男  |
| 1  | sai    | 男  |
| 2  | wenwen | 女  |
+----+--------+------+
4 行於數據集 (0.01 秒)

mysql> commit;
Query OK, 0 rows affected (0.01 秒)

結論

MySql在Read Committed事務隔離級別下是解決了髒讀問題,但是存在不可重複讀和幻讀問題。在Read Committed級別下,MySql InnoDB引擎採用一致性非鎖定讀方案去讀數據,讀不加S鎖,但是寫加了X鎖,所以讀發生了問題,如果使用SELECT * FROM test IN SHARE MODE 主動加S鎖可以避免不可重複讀;

如果主動加S或者X鎖,會發下事務1被阻塞在update的地方,因爲update要加X鎖,row已經被加S鎖,所以要阻塞,事務併發性降低。

如果使用SELECT * FROM test FOR UPDATE 主動加X鎖也可以避免,但是無法避免幻讀。

Repeatable Read級別

Repeatable read 是MySql的默認事務級別,也是網上部分結論出問題的事務級別,該級別在ISO和一些數據庫的實現上是用來解決不可重複讀問題的,但是解決不了幻讀。但是實際上,MySql的可重複讀級別是解決了‘幻讀’和‘不可重複讀’兩個問題的!

實驗結果

事務1結果

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 秒)

mysql> update test set name = 'sai' where id = 1;
Query OK, 1 rows affected (0.02 秒)

mysql> insert test select 5,'h','女';
Query OK, 1 rows affected (0.02 秒)

mysql> commit;
Query OK, 0 rows affected (0.01 秒)

mysql> 

事務2結果

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 行於數據集 (0.02 秒)

mysql> start transaction;
Query OK, 0 rows affected (0.01 秒)

mysql> select * from test ;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 4  | hacker  | 男  |
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
4 行於數據集 (0.02 秒)

mysql> select * from test ;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 4  | hacker  | 男  |
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
4 行於數據集 (0.05 秒)

mysql> select * from test ;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 4  | hacker  | 男  |
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
4 行於數據集 (0.03 秒)

mysql> 
mysql> select * from test ;
+----+---------+------+
| id | name    | sex  |
+----+---------+------+
| 4  | hacker  | 男  |
| 3  | jocker  | 男  |
| 1  | saillen | 男  |
| 2  | wenwen  | 女  |
+----+---------+------+
4 行於數據集 (0.02 秒)

mysql> commit;
Query OK, 0 rows affected (0.01 秒)

mysql> select * from test ;
+----+--------+------+
| id | name   | sex  |
+----+--------+------+
| 5  | h      | 女  |
| 4  | hacker | 男  |
| 3  | jocker | 男  |
| 1  | sai    | 男  |
| 2  | wenwen | 女  |
+----+--------+------+
5 行於數據集 (0.03 秒)

結論

MySql的可重複事務級別解決了不可重複和幻讀問題,這個和大部分的數據庫以及ISO標準是不一樣的。解決方案是採用Next-Key Lock實現,通過鎖定範圍來實現。

以上述例子分析,select * from test; 這裏沒有加where條件,肯定走全表掃描,所以加鎖的範圍就是負無窮到正無窮,如果是加where條件爲id > 10 and id < 20,那麼加鎖範圍就是[10,20]會鎖定id爲10,11,……,20的行,這些行可能不存在,但是在鎖定範圍內是的數據是不會被update和insert的。MySql下解決幻讀的方案存在一定小問題,但是並不嚴重。

串行化

在一般的扁平事務中,MySql的默認事務隔離級別完全夠用了,不需要串行化,串行化是MySql爲分佈式事務準備的。實際使用很少這裏不實驗。

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