MySQL(十一)------ 事務控制和鎖定語句

       表鎖:MyISAM、MEMORY存儲引擎;行鎖:InnoDB存儲引擎;頁鎖:BDB存儲引擎;默認情況下表鎖和行鎖都是自動獲得的,不需要額外的命令;但是有時候用戶需要明確的進行行鎖或者進行事務的控制,以便確保整個事務的完整性,這樣就需要用到事務控制和鎖定語句來完成。

一、LOCK TABLE 和 UNLOCK TABLE

       LOCK TABLE 用於鎖定當前線程的表,UNLOCK TABLE 釋放當前線程的任何鎖定。噹噹前線程想要使用一個被別的線程鎖定的表時會無法得到,只有等待別的線程將表釋放並且當前線程獲得到該表的鎖定之後才能使用;另外還有一個值得注意的地方,當前線程執行另一個 LOCK TABLE 時,或與服務器的連接斷開時,所有由當前線程鎖定的表被隱式的解鎖了。

相關語法如下:

        LOCK TABLES tbl_name [AS alias] { READ [LOCAL] | [LOW_PRIORITY] WRITE} [, ...]  UNLOCK TABLES

  • AS alias:起別名;
  • READ [LOCAL]:READ表示對當前表進行只讀鎖定,當前會話和其它會話都只可讀不可寫(插入、更新等操作);當有關鍵字LOCAL時,表示當前會話只讀不可寫,其它會話可讀可寫;另外,如果是InnoDB類型的表,READ 與 READ LOCAL是等效的,作用都是READ。
  • [LOW_PRIORITY] WRITE:WRITE鎖定表示只有當前會話可讀可寫,LOW_PRIORITY關鍵字會影響鎖定行爲,但是5.6.5版本之後被棄用。

下面舉個例子簡單說明一下READ:

1. 創建表test1,並插入3條數據
mysql> create table test1(id int,name varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test1 values(1001,'kim'),(1002,'bin'),(1003,'jim');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from test1;
+------+------+
| id   | name |
+------+------+
| 1001 | kim  |
| 1002 | bin  |
| 1003 | jim  |
+------+------+
3 rows in set (0.00 sec)

2. 查看當前會話爲1046,在當前會話中鎖定表test1
mysql> select connecction_id();
ERROR 1305 (42000): FUNCTION test1.connecction_id does not exist
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|            1046 |
+-----------------+
1 row in set (0.00 sec)

mysql> show open tables where in_use >= 1;
Empty set (0.00 sec)
 
mysql> lock tables test1 read;                 只讀鎖定
Query OK, 0 rows affected (0.00 sec)

mysql> show open tables where in_use >= 1;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test1    | test1 |      1 |           0 |
+----------+-------+--------+-------------+
1 row in set (0.00 sec)

mysql> select * from test1;                     當前會話下可讀
+------+------+
| id   | name |
+------+------+
| 1001 | kim  |
| 1002 | bin  |
| 1003 | jim  |
+------+------+
3 rows in set (0.00 sec)

mysql> insert into test1 values(1004,'pin');        當前會話下不可寫
ERROR 1099 (HY000): Table 'test1' was locked with a READ lock and can't be updated

3. 打開另外一個cmd窗口作爲一個新的會話,新會話id爲1035.
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|            1035 |
+-----------------+
1 row in set (0.00 sec)

mysql> select * from test1;        新會話可讀
+------+------+
| id   | name |
+------+------+
| 1001 | kim  |
| 1002 | bin  |
| 1003 | jim  |
+------+------+
3 rows in set (0.00 sec)

mysql> insert into test1 values(1004,'pin');     新會話可插入,但此時在堵塞中,等待鎖定被解除時方可執行

4. 1046會話鎖定解除
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

5. 1035會話立即執行插入語句,在此期間一共等待了7分多鐘
mysql> insert into test1 values(1004,'pin');
Query OK, 1 row affected (7 min 44.66 sec)


結論:用READ鎖定表時,當前會話只可讀不可寫,其它會話可讀,寫操作會阻塞

下面舉個例子看一下關鍵字LOCAL的作用:

1. 創建表test2並插入數據,注意,爲了測試關鍵字LOCAL,表的引擎不能時InnoDB
mysql> create table test2(id int,name varchar(15)) engine=MyISAM;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test2 values(1001,'test');
Query OK, 1 row affected (0.00 sec)

mysql> select * from test2;
+------+------+
| id   | name |
+------+------+
| 1001 | test |
+------+------+
1 row in set (0.00 sec)

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|            1046 |
+-----------------+
1 row in set (0.00 sec)

mysql> lock tables test2 read local;           當前會話可讀
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test2;
+------+------+
| id   | name |
+------+------+
| 1001 | test |
+------+------+
1 row in set (0.00 sec)

mysql> insert into test2 values(1002,'kkk');       當前會話不可插入
ERROR 1099 (HY000): Table 'test2' was locked with a READ lock and can't be updated

2. 在會話1035裏對錶test2可讀可寫
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|            1035 |
+-----------------+
1 row in set (0.00 sec)

mysql> select * from test2;
+------+------+
| id   | name |
+------+------+
| 1001 | test |
+------+------+
1 row in set (0.00 sec)

mysql> insert into test2 values(1002,'kkk');
Query OK, 1 row affected (0.00 sec)

結論:使用READ LOCAL關鍵字鎖定表時,當前會話可讀不可寫,其它會話可讀可寫。

         另外,如果使用關鍵字READ鎖定一個表時,其它的會話仍然可以使用READ鎖定相同的表,因爲LOCK TABLES READ是一個共享表,但是,其它的會話使用WRITE鎖的時候會被阻塞,直到READ鎖被釋放纔可以執行。

         如果當前會話用READ鎖定了一個表,那麼再當前會話中該表可以被查看但是無法查看別的表;比如用read鎖定了test1表,此時可以查看test1表但無法查看test2表。

         如果一個表被WRITE鎖定,那麼當前會話中該表可讀可寫,但是其它會話中的讀寫操作都會被阻塞,而且其它會話也無法對該表進行READ鎖定或WRITE鎖定,都被阻塞了。

         如果表1被鎖定,當它再去鎖定表2時那麼表1就會自動解鎖,斷開數據庫鏈接時表1也會自動解鎖;如果想要同時鎖定多個表怎麼辦呢?看鎖定表的語法,可以用該語法一次鎖定多個表。

         如果對一個視圖表、臨時表、觸發器進行鎖定,那麼它會將視圖定義時查詢的相關表、觸發器內的所有表都加上鎖。

二、事務控制

       默認情況下MySQL是自動提交的,比如我們建一個表,當我們執行向表裏插入數據的語句時,一旦執行成功,那麼此結果就被自動提交了,表裏就多了新插入的數據;但有時候我們不想它自動提交該怎麼辦呢,這時候就需要使用顯示的命令來提交,相關命令如下:

        START TRANSACTION | BEGIN [WORK]

        COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]

        ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]

        SET AUTOCOMMIT = {0 | 1}

  • START TRANSACTION 或 BEGIN 語句用來開始一項新的事務;
  • COMMIT 和 ROLLBACK 用來提交、回滾事務;
  • CHAIN 和 RELEASE 字句分別用來定義事務在提交或回滾之後的操作,CHAIN 會立即啓動一個新事物,並且和剛纔的事務具有相同的隔離級別,RELEASE則會斷開和客戶端的連接;
  • SET AUTOCOMMIT 修改當前連接的提交方式,如果設置爲0,那麼自此之後所有的事務都需要通過明確的命令進行提交或回滾。

下面用用一個例子說明一下事務控制的過程,表格的兩列代表兩個不同的會話,同一行代表當前時刻不同會話的操作;下面的例子將會用到actor表,當前情況下actor表的內容如下:

mysql> select * from actor;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        1 | a          | a         |
|        2 | b          | b         |
|        3 | c          | c         |
|        4 | d          | d         |
|        5 | e          | e         |
+----------+------------+-----------+
5 rows in set (0.01 sec)
會話1 會話2
mysql> select * from actor where actor_id=6;    
Empty set (0.00 sec)
mysql> select * from actor where actor_id = 6;
Empty set (0.00 sec)

開啓事務並插入一條記錄,此時並沒有提交

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

mysql> insert into actor values(6,'f','f');
Query OK, 1 row affected (0.00 sec)

mysql> select * from actor where actor_id=6;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        6 | f          | f         |
+----------+------------+-----------+
1 row in set (0.00 sec)

 
 

查詢actor表,此條記錄依然爲空

mysql> select * from actor where actor_id = 6;
Empty set (0.00 sec)

執行提交

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

 
 

此時可以查到這條記錄

mysql> select * from actor where actor_id = 6;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        6 | f          | f         |
+----------+------------+-----------+

上面的事務提交或回滾後,後面的事務仍然自動提交

mysql> insert into actor values(7,'g','g');
Query OK, 1 row affected (0.01 sec)

 
 

可以查詢到插入的數據

mysql> select * from actor where actor_id = 7;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        7 | g          | g         |
+----------+------------+-----------+

重新開啓一個事務

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

mysql> insert into actor values(8,'h','h');
Query OK, 1 row affected (0.00 sec)

提交後再開啓一個新事務

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

此時開啓了一個新事務

mysql> insert into actor values(9,'j','i');
Query OK, 1 row affected (0.00 sec)

 
 

可以看到8這條記錄能查到,9這條記錄就沒有

mysql> select * from actor where actor_id = 8;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        8 | h          | h         |
+----------+------------+-----------+
1 row in set (0.00 sec)

mysql> select * from actor where actor_id = 9;
Empty set (0.00 sec)

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

提交過後,此時能夠查到

mysql> select * from actor where actor_id = 9;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|        9 | j          | i         |
+----------+------------+-----------+
1 row in set (0.00 sec)

           如果在鎖表期間用start transaction命令開始一個新事物,此時會造成隱含的unlock tables被執行,看下面的例子:

會話1 會話2

actor表中暫無第10條記錄

mysql> select * from actor where actor_id=10;
Empty set (0.00 sec)

mysql> select * from actor where actor_id = 10;
Empty set (0.00 sec)

在當前會話對actor表加write鎖

mysql> lock table actor write;
Query OK, 0 rows affected (0.00 sec)

 
 

此時,該會話對actor表的讀寫操作均被阻塞

mysql> select * from actor where actor_id = 10;
等待

插入一條數據:

mysql> insert into actor values(10,'j','j');
Query OK, 1 row affected (0.00 sec)

等待

回滾:

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

等待

重新開啓一個事務:

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

等待
 

會話1開啓一個新事務的同時,表鎖被釋放,此時可以查詢到:

mysql> select * from actor where actor_id = 10;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       10 | j          | j         |
+----------+------------+-----------+
1 row in set (3 min 32.01 sec)

對lock方式加的表鎖不能通過rollback進行回滾

         注意,提交、回滾的操作只能對事務類型的表,所有的DDL語句是不能回滾的,另外,部分DDL語句還會造成隱式的提交。

        在事務中我們還可以定義SAVEPOINT來對當前點進行標記,然後可以通過回滾操作來退回到指定的點,這樣我們可以定義多個不同的SAVEPOINT來實現不同節點的回滾;注意,如果SAVEPOINT定義了相同的名字,那麼後面的會覆蓋掉前面的定義;對於不再需要的SAVEPOINT可以通過RELEASE SAVEPOINT命令來刪除。

舉個例子:

會話1 會話2

查詢第11條記錄,結果爲空

mysql> select * from actor where actor_id=11;
Empty set (0.00 sec)

第11條記錄同樣爲空

mysql> select * from actor where actor_id = 11;
Empty set (0.00 sec)

啓動一個事務,向actor表中插入數據

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

mysql> insert into actor values(11,'k','k');
Query OK, 1 row affected (0.00 sec)

 

可以查詢到剛剛插入的記錄

mysql> select * from actor where actor_id=11;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       11 | k          | k         |
+----------+------------+-----------+
1 row in set (0.00 sec)

無法查詢到會話1剛插入的記錄

mysql> select * from actor where actor_id = 11;
Empty set (0.00 sec)

定義savepoint,名爲test

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

繼續插入一條數據

mysql> insert into actor values(12,'l','l');
Query OK, 1 row affected (0.00 sec)

 

可以查詢到插入的兩條記錄:

mysql> select * from actor where actor_id=11 or actor_id = 12;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       11 | k          | k         |
|       12 | l          | l         |
+----------+------------+-----------+
2 rows in set (0.00 sec)

依然無法查詢到結果:

mysql> select * from actor where actor_id = 11 or actor_id = 12;
Empty set (0.00 sec)

回滾到剛纔定義的savepoint點

mysql> rollback to savepoint test;
Query OK, 0 rows affected (0.00 sec)

 

此時只能找到第11條記錄,第12條已經被回滾了:

mysql> select * from actor where actor_id=11 or actor_id = 12;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       11 | k          | k         |
+----------+------------+-----------+
1 row in set (0.00 sec)

依然無結果:

mysql> select * from actor where actor_id = 11 or actor_id = 12;
Empty set (0.00 sec)

提交:

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

 

只能查到第11條記錄

mysql> select * from actor where actor_id=11 or actor_id = 12;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       11 | k          | k         |
+----------+------------+-----------+
1 row in set (0.00 sec)

同樣只能查到第11條記錄

mysql> select * from actor where actor_id = 11 or actor_id = 12;
+----------+------------+-----------+
| actor_id | first_name | last_name |
+----------+------------+-----------+
|       11 | k          | k         |
+----------+------------+-----------+
1 row in set (0.00 sec)

三、分佈式事務的使用

       MySQL從5.0.3開始支持分佈式事務,且目前只有InnoDB存儲引擎支持。一個分佈式事務會涉及多個行動,這些行動本身是事務性的,所有行動必須一起成功完成或者一起被回滾。

     3.1 分佈式事務原理

        MySQL中分佈式事務的應用程序涉及一個或多個資源管理器和一個事務管理器:

  • 資源管理器(resource manager):用來管理系統資源,是通向事務資源的途徑。數據庫就是一種資源管理器。資源管理還應該具有管理事務提交或回滾的能力。例如多臺MySQL服務器或與其它服務器的組合都可以作爲資源管理器。
  • 事務管理器(transaction manager):事務管理器是分佈式事務的核心管理者。事務管理器與每個資源管理器(resource 
    manager)進行通信,協調並完成事務的處理。事務的各個分支由唯一命名進行標識。

      例如:mysql在執行分佈式事務(外部XA)的時候,mysql服務器相當於xa事務資源管理器,與mysql鏈接的客戶端相當於事務管理器。

        分佈式事務通常採用2PC協議,全稱Two Phase Commitment Protocol。該協議主要爲了解決在分佈式數據庫場景下,所有節點間數據一致性的問題。分佈式事務通過2PC協議將提交分成兩個階段:

  • 階段一爲準備(prepare)階段:即所有的參與者準備執行事務並鎖住需要的資源。參與者ready時,向transaction manager報告已準備就緒。 
  • 階段二爲提交階段(commit):當transaction manager確認所有參與者都ready後,向所有參與者發送commit命令,否則發送回滾命令rollback。 

如下圖所示:

      3.2 分佈式事務語法

         分佈式事務(XA事務)的語法如下:

         XA { START | BEGIN } xid [JOIN | RESUME]    啓動一個XA事務(包含一個唯一事務標識符xid )

         XA END xid [SUSPEND [FOR MIGRATE]]     結束xid事務

         XA PREPARE  xid      準備、預提交xid事務

         XA COMMIT xid [ONE PHASE] 提交xid事務 

         XA ROLLBACK xid 回滾xid事務 

         XA RECOVER 查看處於PREPARE 階段的所有事務的詳細信息

xid的值唯一,它包含 gtrid [, bqual [, formatID ]] 3個部分:

  • gtrid 是一個分佈式事務標識符,相同的分佈式事務應該使用相同的gtrid,這樣可以明確知道XA事務屬於哪個分佈式任務;
  • bqual 是一個分支限定符,默認值是空串;對於一個分佈式事務中的每個分支事務,bqual的值必須唯一;
  • formatID 是一個數字,用於標識由gtrid和bqual值使用的格式,默認值是1。

舉個例子具體說明,在這裏沒有使用多個不同的數據庫,只是使用了MySQL數據庫裏面的兩個庫test1和mydb1:

test1庫中的會話1 mydb1庫中的會話2

在數據庫test1中啓動一個分佈式事務的一個分支事務

mysql> xa start 'test','test1';
Query OK, 0 rows affected (0.00 sec)

分支事務1在actor表中插入一條記錄:

mysql> insert into actor values(13,'l','l');
Query OK, 1 row affected (0.00 sec)

在數據庫mydb1中啓動分佈式事務‘test’的另外一個分支事務

mysql> xa start 'test','mydb1';
Query OK, 0 rows affected (0.00 sec)

分支事務2在表mydb_test中插入兩條記錄

mysql> insert into mydb_test values(1,'test1'),(2,'test2');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

對分支事務1進行第一階段提交,進入prepare狀態:

mysql> xa end 'test','test1';
Query OK, 0 rows affected (0.00 sec)

mysql> xa prepare 'test','test1';
Query OK, 0 rows affected (0.00 sec)

對分支事務2進行第一階段提交,進入prepare狀態:

mysql> xa end 'test','mydb1';
Query OK, 0 rows affected (0.00 sec)

mysql> xa prepare 'test','mydb1';
Query OK, 0 rows affected (0.00 sec)

查看出於prepare狀態的事務

mysql> xa recover \G
*************************** 1. row ***************************
    formatID: 1
gtrid_length: 4
bqual_length: 5
        data: testmydb1
*************************** 2. row ***************************
    formatID: 1
gtrid_length: 4
bqual_length: 5
        data: testtest1
2 rows in set (0.00 sec)

查看出於prepare狀態的事務

mysql> xa recover \G;
*************************** 1. row ***************************
    formatID: 1
gtrid_length: 4
bqual_length: 5
        data: testmydb1
*************************** 2. row ***************************
    formatID: 1
gtrid_length: 4
bqual_length: 5
        data: testtest1
2 rows in set (0.00 sec)

兩個事務都進入準備提交階段,如果提交遇到錯誤,應該回滾所有的分支以確保分佈式任務的正確

提交分支事務1:

mysql> xa commit 'test','test1';
Query OK, 0 rows affected (0.00 sec)

提交分支事務2:

mysql> xa commit 'test','mydb1';
Query OK, 0 rows affected (0.00 sec)

     3.3 存在的問題

        MySQL5.7.7版本之前,它支持的分佈式是有缺陷的,具體表現在:

  • 已經prepare的事務,在客戶端異常退出或者服務宕機的時候,2PC的事務會被回滾;
  • 在服務器故障重啓提交後,相應的Binlog被丟失。

        但是在5.7.7版本之後,這個bug被修復了,後面使用MySQL的分佈式事務就有了較好的支持。

兩個版本之間的問題及如何修復解決的可以參見博客https://blog.csdn.net/hzrandd/article/details/50688437,裏面有較好的解釋。

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