innodb引擎下事務隔離級別與幻讀

一、事務的acid特性

  • 原子性:一個事務必須被視爲一個不可分割的最小工作單元,事務中所有操作要麼全部操作成功,要麼全部失敗回滾;
  • 一致性:數據庫總是從一個一致性的狀態轉換到另一個一致性的狀態;
  • 隔離性:通常來說,一個事務所做的修改在其提交之前,對其他事務是不可見的;
  • 持久性:一旦事務提交,所做的修改就會永久保存早數據庫中,即使此時系統崩潰,修改部分也不會丟失。

        在sql標準中定義了四種隔離級別,每一種級別都規定了一個事務中所做的修改在另一個事務中是可見的還是不可見的。較低級別的隔離通常可以執行更高級別的併發,系統的開銷也更低。

二、隔離級別

  • read uncommitted(未提交讀)

        在read uncommitted級別,事務中的修改,即使還沒有提交,在其他事務中也是可見的,也稱作“髒讀”,實際中很少使用。先看看數據庫當前隔離級別並更改爲read uncommitted級別:

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

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

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

        然後同時開啓事務1和事務2。在事務1中先查看某條數據,然後在事務2中修改該條數據,暫不提交,這時在事務1中再次查看這條數據,發現數據變化了的,這時就發生“髒讀”了。

#事務1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
                                               #事務2
                                               mysql> start transaction;
                                               Query OK, 0 rows affected (0.00 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 111111 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               mysql> update qh_user set phone = '222222' where id = 1;
                                               Query OK, 1 row affected (0.02 sec)
                                               Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 222222 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
  • read committed(提交讀/不可重複讀)

        在read committed級別,一個事務開始後,只能看見其他已經提交事務所做的修改。所以在一個事務中某條數據前後兩次查看可能是不同的,原因就在於這兩次查看之間有其他的事務對該條數據作了修改並提交了。

        先還是將事務設置成read committed級別:

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

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)

        此處也是同時開啓兩個事務,先在事務1中查看某條數據,在事務2中修改該條數據,然後事務1中再查看該條數據,這時再提交事務2,最後再第三次在事務1中查看數據,會發現第二次查看和第三次查看數據是不一樣的,也就是不可重複讀。

#事務1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
                                               #事務2
                                               mysql> start transaction;
                                               Query OK, 0 rows affected (0.00 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  | 
+----+------+--------+----------+
|  1 | jack | 111111 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               mysql> update qh_user set phone = '333333' where id = 1;
                                               Query OK, 1 row affected (0.00 sec)
                                               Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 111111 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               mysql> commit;
                                               Query OK, 0 rows affected (0.01 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
  • repeatable read(可重複讀)

        在repeatable read級別,在一個事務中讀某條數據,不管在另一個事務中是修改(update)還是刪除(delete),在原事務中都是不可見的,也就是說多次讀取的結果都是一致的,部分解決了不可重複讀的問題。還是先設置隔離級別:

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

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

        還是同時開啓四個事務1、2、3、4,先在事務1中查看某條數據,在事務2中修改並提交,再查看事務1中數據,應該是沒有變化,此時在事務3中刪除這條記錄,再查看事務1中數據,應該也是沒變化,最後再在事務4中插入一條記錄,此時理論上在事務1中應該會看到多出一條數據,也就出現了“幻讀”,但是innodb存儲引擎已通過mvcc解決了“幻讀”問題,所以實際上還是沒變化。

#事務1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
                                               
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               #事務2
                                               mysql> start transaction;
                                               Query OK, 0 rows affected (0.00 sec)
                                               mysql> update qh_user set phone = '1111' where id = 1;
                                               Query OK, 1 row affected (0.00 sec)
                                               Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               #事務3
                                               mysql> start transaction;
                                               Query OK, 0 rows affected (0.00 sec)

                                               mysql> delete from qh_user where id = 1;
                                               Query OK, 1 row affected (0.00 sec)

                                               mysql> commit;
                                               Query OK, 0 rows affected (0.01 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                               #事務4
                                               mysql> start transaction;
                                               Query OK, 0 rows affected (0.00 sec)

                                               mysql> insert into qh_user(name, phone, address)
                                                   -> values ('rose', '55555', 'beijing');
                                               Query OK, 1 row affected (0.00 sec)

                                               mysql> commit;
                                               Query OK, 0 rows affected (0.00 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
  • serializable(可串行化)

        其實在repeatable read級別,只是部分解決了“幻讀”問題,雖然在事務1中沒看到新增的記錄,但實際上是可以修改這條記錄的。

#事務1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  | 
+----+------+--------+----------+
|  1 | jack | 333333 | new york |
+----+------+--------+----------+
1 row in set (0.00 sec)
                                                #事務2
                                                mysql> start transaction;
                                                Query OK, 0 rows affected (0.00 sec)

                                                mysql> insert into qh_user(name, phone, address) 
                                                    -> values ('david', '8888', 'toronto');
                                                Query OK, 1 row affected (0.00 sec)

                                                mysql> select * from qh_user;
                                                +----+-------+--------+----------+
                                                | id | name  | phone  | address  |
                                                +----+-------+--------+----------+
                                                |  1 | jack  | 333333 | new york |
                                                |  2 | david | 8888   | toronto  |
                                                +----+-------+--------+----------+
                                                2 rows in set (0.00 sec)

                                                mysql> commit;
                                                Query OK, 0 rows affected (0.01 sec)
mysql> select * from qh_user;
+----+------+--------+----------+
| id | name | phone  | address  |
+----+------+--------+----------+
|  1 | jack | 333333 | new york | 
+----+------+--------+----------+
1 row in set (0.00 sec)

mysql> update qh_user set address = 'california' where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

        從上述實驗結果可以看到,雖然在事務1中select沒有id=2的記錄,但是直接修改這條數據,數據庫返回的依然是ok的,所以說mvcc並沒有完全解決幻讀問題。而在serializable級別每讀取一行數據都會加鎖,是最高的隔離級別,這樣就可以徹底解決“幻讀”問題。

#事務1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from qh_user;
+----+-------+--------+------------+
| id | name  | phone  | address    | 
+----+-------+--------+------------+
|  1 | jack  | 333333 | new york   |
|  2 | david | 8888   | california |
+----+-------+--------+------------+
2 rows in set (0.00 sec)
                                                #事務2
                                                mysql> start transaction;
                                                Query OK, 0 rows affected (0.00 sec)

                                                mysql> insert into qh_user(name, phone, address)
                                                    -> values ('floyd', '55555', 'beijing');
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
                                                Query OK, 1 row affected (25.69 sec)

        由上述實驗結果可以看到,在設置serializable級別後,在事務2中插入一條新紀錄後,並沒有馬上顯示insert成功,而是阻塞在那裏,直到事務1執行commit後,事務2才提示query ok

        由此可見,“幻讀”問題是解決了,但是這極可能會導致大量超時和鎖競爭問題,實際中也很少應用。只有在必須確保數據一致性並且可以接受沒有併發情況下才會考慮使用該級別。

 

 

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