1. 程式人生 > >Mysql隔離級別與鎖

Mysql隔離級別與鎖

--一、隔離級別 4種模式

1、序列化讀(SERIALIZABLE)

保證可序列化的排程,讀資料加表的共享鎖,寫資料加表的排它鎖,降低併發,影響效率

2、未提交讀(READ-UNCOMMITTED)        

允許讀未提交的資料

可能會造成:幻讀、不可重複讀、髒讀

3、已提交讀(READ-COMMITTED)    (Oracle預設方式)                               

只允許讀已提交的事務,但不可要求重複讀(在一個事務兩次讀取同一個資料項期間 讀取的資料可以發生變化,當另一個事務更新了該資料並提交時)

可能會造成:幻讀、不可重複讀

4、可重複讀(REPEATABLE-READ)   (Mysql預設方式)   

只允許讀已提交的事務,在一個事務讀取同一個資料項期間 讀取的都是事務剛開始看到的資料,不會發生變化

1)髒讀:一個事務讀到了其他事務還沒有提交的資料

2)幻讀:一個事務讀到了其他事務新增的資料(insert)

3)不可重複讀:一個事務讀到了其他事務針對舊資料的修改記錄(update、delete)

--當前設定為 手動提交

mysql> show variables like 'autocommit';

+---------------+-------+

| Variable_name | Value |

+---------------+-------+

| autocommit    | OFF   |

+---------------+-------+

1 row in set (0.00 sec)



1)髒讀:

隔離級別為 未提交讀(READ-UNCOMMITTED) 

--設定隔離級別為 未提交讀(READ-UNCOMMITTED)

mysql> set session tx_isolation='READ-UNCOMMITTED';

Query OK, 0 rows affected (0.01 sec)


mysql> show variables like 'tx_isolation';

+---------------+------------------+

| Variable_name | Value            |

+---------------+------------------+

| tx_isolation  | READ-UNCOMMITTED |

+---------------+------------------+

1 row in set (0.00 sec)


--session1 建立測試表

mysql> create table test_dirty_read_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.03 sec)


mysql> insert into test_dirty_read_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--session2 讀取test_dirty_read_table測試表  有資料

mysql> select * from test_dirty_read_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)


--session1 插入一條id為4的記錄,但不提交

mysql> insert into test_dirty_read_table values (4,'ddd',60);

Query OK, 1 row affected (0.00 sec)


--session2 出現髒讀,讀到了session1還未提交的資料

mysql>  select * from test_dirty_read_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

|    4 | ddd  |    60 |

+------+------+-------+

4 rows in set (0.00 sec)




2)幻讀:

隔離級別為 未提交讀(READ-UNCOMMITTED)  或者 已提交讀(READ-COMMITTED)

--這裡設定隔離級別為 已提交讀(READ-COMMITTED)

mysql> set session tx_isolation='READ-COMMITTED';

Query OK, 0 rows affected (0.00 sec)


mysql> show variables like 'tx_isolation';

+---------------+----------------+

| Variable_name | Value          |

+---------------+----------------+

| tx_isolation  | READ-COMMITTED |

+---------------+----------------+

1 row in set (0.00 sec)


--session1 建立測試表

mysql> create table test_phantom_problem_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.01 sec)


mysql> insert into test_phantom_problem_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--session2 開始一個事務 ,讀取 test_phantom_problem_table 測試表  有資料

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> select * from test_phantom_problem_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)



--session1 開始一個事務,插入一條id為4的記錄,提交

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> insert into test_phantom_problem_table values (4,'ddd',60);

Query OK, 1 row affected (0.00 sec)


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--session2 還是在當前事務中,但讀取 test_phantom_problem_table 測試表 出現了id為4的資料, 出現幻讀,讀到了session1新增的資料

mysql> select * from test_phantom_problem_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

|    4 | ddd  |    60 |

+------+------+-------+

4 rows in set (0.00 sec)




3)不可重複讀:與幻讀類似,不過是update和delete操作




4)可重複讀:

隔離級別為 可重複讀(REPEATABLE-READ) 

--設定隔離級別為 可重複讀(REPEATABLE-READ)

mysql> set session tx_isolation='REPEATABLE-READ';

Query OK, 0 rows affected (0.00 sec)


mysql> show variables like 'tx_isolation';

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)


--session1 建立測試表

mysql> create table test_repeatable_read_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.02 sec)


mysql> insert into test_repeatable_read_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--session2 開始一個事務 ,讀取 test_repeatable_read_table測試表  有資料

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> select * from test_repeatable_read_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)




--session1 開始一個事務,插入一條id為4的記錄,提交

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> insert into test_repeatable_read_table values (4,'ddd',60);

Query OK, 1 row affected (0.00 sec)


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--session2 還是在當前事務中,但讀取 test_repeatable_read_table 測試表時, 每次都是事務剛開始看到的資料,不會發生變化

mysql> select * from test_repeatable_read_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)


--但有兩種方式可以檢視到session1修改的資料

--(1)可以for update讀取最新版本號, 可以檢視到session1修改的資料

mysql> select * from test_repeatable_read_table for update;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

|    4 | ddd  |    60 |

+------+------+-------+

4 rows in set (0.00 sec)


--(2)退出當前事務後(commit),可以檢視到session1修改的資料

mysql> commit;

Query OK, 0 rows affected (0.00 sec)


mysql> select * from test_repeatable_read_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

|    4 | ddd  |    60 |

+------+------+-------+

4 rows in set (0.00 sec)

--二、鎖  3種模式

1、Record Lock        索引上加鎖,鎖住單個記錄行key (隔離級別為 READ-COMMITTED的情況下 只有Record Lock鎖;隔離級別為REPEATABLE-READ的情況下,主鍵和唯一索引都降級為此Record Lock鎖)

2、Gap Lock             間隙鎖,鎖定一個範圍 但不包括記錄本身

3、Next-Key Lock    Gap Lock + Record Lock  鎖定一個範圍 且包括記錄本身 (隔離級別為 REPEATABLE-READ的情況下 預設為Next-Key Lock 鎖,同樣也是Mysql預設方式)

鎖型別:共享鎖,排它鎖 (這裡測試用以下作為共享鎖,排它鎖的測試)

select ......lock in share mode 產生共享鎖

select ......for update               產生排它鎖

1、Record Lock 鎖

1)隔離級別為 READ-COMMITTED的情況下

--設定隔離級別為 已提交讀(READ-COMMITTED)

mysql> set session tx_isolation='READ-COMMITTED';

Query OK, 0 rows affected (0.00 sec)


--當前的隔離級別

mysql> show variables like 'tx_isolation';

+---------------+----------------+

| Variable_name | Value          |

+---------------+----------------+

| tx_isolation  | READ-COMMITTED |

+---------------+----------------+

1 row in set (0.00 sec)



--session1 建立測試表

mysql> create table test_record_lock_noindex_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.07 sec)


mysql> insert into test_record_lock_noindex_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--檢視當前資料

mysql> select * from test_record_lock_noindex_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)


--更新無索引的一行資料

mysql> update test_record_lock_noindex_table set score=100 where name='aaa';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0


--執行計劃為全表

mysql> explain update test_record_lock_noindex_table set score=100 where name='aaa';

+----+-------------+--------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

| id | select_type | table                          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |

+----+-------------+--------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

|  1 | UPDATE      | test_record_lock_noindex_table | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where |

+----+-------------+--------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

1 row in set (0.03 sec)



--session2更新相同表 不同行 ,成功執行,不發生表鎖

mysql> update test_record_lock_noindex_table set score=100 where name='bbb';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0


--session1提交

mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--session2提交

mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--session2檢視修改後的資料 正常

mysql> select * from test_lock_noindex_table_commit;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |   100 |

|    2 | bbb  |   100 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.03 sec)



2)隔離級別為REPEATABLE-READ的情況下,主鍵和唯一索引都降級為此Record Lock鎖

--設定隔離級別為 可重複讀(REPEATABLE-READ)

mysql> set session tx_isolation='REPEATABLE-READ';

Query OK, 0 rows affected (0.00 sec)


--當前的隔離級別

mysql> show variables like 'tx_isolation';

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)



--session1 建立測試表,name欄位為唯一索引

mysql> create table test_record_lock_ukindex_table (id int  ,name varchar(5),score int, unique key uk_name(name));

Query OK, 0 rows affected (0.08 sec)


mysql> insert into test_record_lock_ukindex_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--檢視當前資料

mysql> select * from test_record_lock_ukindex_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)



--更新有唯一索引的一行資料

mysql> update test_record_lock_ukindex_table set score=100 where name='aaa';

Query OK, 1 row affected (0.04 sec)

Rows matched: 1  Changed: 1  Warnings: 0


--執行計劃為索引範圍掃描

mysql> explain update test_record_lock_ukindex_table set score=100 where name='aaa';

+----+-------------+--------------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

| id | select_type | table                          | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |

+----+-------------+--------------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

|  1 | UPDATE      | test_record_lock_ukindex_table | NULL       | range | uk_name       | uk_name | 8       | const |    1 |   100.00 | Using where |

+----+-------------+--------------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

1 row in set (0.00 sec)



--session2更新相同表 不同行 ,成功執行,不發生表鎖

mysql> update test_record_lock_ukindex_table set score=100 where name='bbb';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0



--session1提交

mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--session2提交

mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--session2檢視修改後的資料 正常

mysql> select * from test_record_lock_ukindex_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |   100 |

|    2 | bbb  |   100 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)




3、Next-Key Lock  隔離級別為 REPEATABLE-READ的情況下 預設為Next-Key Lock 鎖

1)普通索引的情況下,會產生範圍鎖

--設定隔離級別為 可重複讀(REPEATABLE-READ)

mysql> set session tx_isolation='REPEATABLE-READ';

Query OK, 0 rows affected (0.00 sec)


--當前的隔離級別

mysql> show variables like 'tx_isolation';

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)



--session1 建立測試表,name為普通索引

mysql> create table test_nextkey_lock_index_table (id int  ,name varchar(5),score int,index idx_name(name) );

Query OK, 0 rows affected (0.25 sec)


mysql> insert into test_nextkey_lock_index_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--檢視當前資料

mysql> select * from test_nextkey_lock_index_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)



--更新有普通索引的一行資料 name為aaa, 此時發生Next-Key Lock鎖,其中gap鎖定了 (- ∞,aaa) 和(aaa,bbb) 的範圍,record lock 鎖定本身的aaa

mysql> update test_nextkey_lock_index_table set score=100 where name='aaa';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0


--執行計劃為全表掃

mysql> explain update test_nextkey_lock_index_table set score=100 where name='aaa';

+----+-------------+-------------------------------+------------+-------+---------------+----------+---------+-------+------+----------+-------------+

| id | select_type | table                         | partitions | type  | possible_keys | key      | key_len | ref   | rows | filtered | Extra       |

+----+-------------+-------------------------------+------------+-------+---------------+----------+---------+-------+------+----------+-------------+

|  1 | UPDATE      | test_nextkey_lock_index_table | NULL       | range | key_name      | key_name | 8       | const |    1 |   100.00 | Using where |

+----+-------------+-------------------------------+------------+-------+---------------+----------+---------+-------+------+----------+-------------+

1 row in set (0.02 sec)



--session2更新相同表 不同行 ,沒有產生鎖,因為name欄位為bbb和ccc 不在 (- ∞,aaa) 和(aaa,bbb) 的範圍 也不是aaa本身

mysql> update test_nextkey_lock_index_table set score=100 where name='bbb';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0


mysql> update test_nextkey_lock_index_table set score=100 where name='ccc';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0



--session2 已共享鎖的方式讀session1修改的資料失敗,因為name欄位為aaa的 被record lock鎖定了本身

mysql> select * from test_nextkey_lock_index_table where name='aaa' lock in share mode ;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


--session2插入一條name欄位位於aaa和bbb之間的資料abb 失敗,因為abb位於(aaa,bbb) 的範圍

mysql> insert into test_nextkey_lock_index_table values (2,'abb',80);  

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


-- session2插入一條name欄位大於bbb的資料bcc 成功,因為bcc不在 (- ∞,aaa) 和(aaa,bbb) 的範圍 也不是aaa本身

mysql> insert into test_nextkey_lock_index_table values (2,'bcc',80);

Query OK, 1 row affected (0.00 sec)




2)無索引的情況下,會發生表鎖

--設定隔離級別為 可重複讀(REPEATABLE-READ)

mysql> set session tx_isolation='REPEATABLE-READ';

Query OK, 0 rows affected (0.00 sec)


--當前的隔離級別

mysql> show variables like 'tx_isolation';

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)



--session1 建立測試表

mysql> create table test_nextkey_lock_noindex_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.06 sec)


mysql> insert into test_nextkey_lock_noindex_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)



--檢視當前資料

mysql> select * from test_nextkey_lock_noindex_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)



--更新無索引的一行資料

mysql> update test_nextkey_lock_noindex_table set score=100 where name='aaa';

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0


--執行計劃為全表掃

mysql> explain update test_nextkey_lock_noindex_table set score=100 where name='aaa';

+----+-------------+---------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

| id | select_type | table                           | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |

+----+-------------+---------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

|  1 | UPDATE      | test_nextkey_lock_noindex_table | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where |

+----+-------------+---------------------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

1 row in set (0.00 sec)



--session2更新相同表 不同行 ,發生表鎖

mysql> update test_nextkey_lock_noindex_table set score=100 where name='bbb';

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


mysql> update test_nextkey_lock_noindex_table set score=100 where name='ccc';

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction



4、MDL鎖(meta data lock):保證表中的元資料不被修改

--session1 建立測試表

mysql> create table test_mdl_table (id int  ,name varchar(5),score int);

Query OK, 0 rows affected (0.09 sec)


mysql> insert into test_mdl_table values (1,'aaa',75),(2,'bbb',80),(3,'ccc',88);

Query OK, 3 rows affected (0.00 sec)

Records: 3  Duplicates: 0  Warnings: 0


mysql> commit;

Query OK, 0 rows affected (0.00 sec)


--session1 開始一個事務,並查詢資料

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> select * from test_mdl_table;

+------+------+-------+

| id   | name | score |

+------+------+-------+

|    1 | aaa  |    75 |

|    2 | bbb  |    80 |

|    3 | ccc  |    88 |

+------+------+-------+

3 rows in set (0.00 sec)



--session2 開始一個事務 ,DDL操作發現被鎖住

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)


mysql> alter table test_mdl_table  add class varchar(5);

鎖等待


--session3 檢視當前的程序,有MDL鎖

mysql> show full processlist;

+----+-----------------+-----------+-------+---------+------+---------------------------------+--------------------------------------------------+

| Id | User            | Host      | db    | Command | Time | State                           | Info                                             |

+----+-----------------+-----------+-------+---------+------+---------------------------------+--------------------------------------------------+

|  1 | event_scheduler | localhost | NULL  | Daemon  | 1530 | Waiting on empty queue          | NULL                                             |

|  4 | root            | localhost | flydb | Sleep   |  146 |                                 | NULL                                             |

|  5 | root            | localhost | flydb | Query   |   76 | Waiting for table metadata lock | alter table test_mdl_table  add class varchar(5) |

|  6 | root            | localhost | flydb | Query   |    0 | starting                        | show full processlist                            |

+----+-----------------+-----------+-------+---------+------+---------------------------------+--------------------------------------------------+

4 rows in set (0.02 sec)