表鎖: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版本之后被棄用。
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 | +------+------+ rows in set (0.00 sec) 2. 查看當前會話為1046,在當前會話中鎖定表test1 mysql> select connection_id(); +-----------------+ | connection_id() | +-----------------+ | 1046 | +-----------------+ 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 | +----------+-------+--------+-------------+ row in set (0.00 sec) mysql> select * from test1; 當前會話下可讀 +------+------+ | id | name | +------+------+ | 1001 | kim | | 1002 | bin | | 1003 | jim | +------+------+ 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 | +-----------------+ row in set (0.00 sec) mysql> select * from test1; 新會話可讀 +------+------+ | id | name | +------+------+ | 1001 | kim | | 1002 | bin | | 1003 | jim | +------+------+ 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鎖定表時,當前會話只可讀不可寫,其它會話可讀,寫操作會阻塞
另外,如果使用關鍵字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表的內容如下:
會話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; mysql> insert into actor values(6,'f','f'); mysql> select * from actor where actor_id=6; |
|
查詢actor表,此條記錄依然為空 mysql> select * from actor where actor_id = 6; |
|
執行提交 mysql> commit; |
|
此時可以查到這條記錄 mysql> select * from actor where actor_id = 6; |
|
上面的事務提交或回滾后,后面的事務仍然自動提交 mysql> insert into actor values(7,'g','g'); |
|
可以查詢到插入的數據 mysql> select * from actor where actor_id = 7; |
|
重新開啟一個事務 mysql> start transaction; mysql> insert into actor values(8,'h','h'); 提交后再開啟一個新事務 mysql> commit and chain; 此時開啟了一個新事務 mysql> insert into actor values(9,'j','i'); |
|
可以看到8這條記錄能查到,9這條記錄就沒有 mysql> select * from actor where actor_id = 8; mysql> select * from actor where actor_id = 9; |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) |
|
提交過后,此時能夠查到 mysql> select * from actor where actor_id = 9; |
如果在鎖表期間用start transaction命令開始一個新事物,此時會造成隱含的unlock tables被執行,看下面的例子:
會話1 | 會話2 |
---|---|
actor表中暫無第10條記錄 mysql> select * from actor where actor_id=10; |
mysql> select * from actor where actor_id = 10; Empty set (0.00 sec) |
在當前會話對actor表加write鎖 mysql> lock table actor write; |
|
此時,該會話對actor表的讀寫操作均被阻塞 mysql> select * from actor where actor_id = 10; |
|
插入一條數據: mysql> insert into actor values(10,'j','j'); |
等待 |
回滾: mysql> rollback; |
等待 |
重新開啟一個事務: mysql> start transaction; |
等待 |
會話1開啟一個新事務的同時,表鎖被釋放,此時可以查詢到: mysql> select * from actor where actor_id = 10; 對lock方式加的表鎖不能通過rollback進行回滾 |
注意,提交、回滾的操作只能對事務類型的表,所有的DDL語句是不能回滾的,另外,部分DDL語句還會造成隱式的提交。
在事務中我們還可以定義SAVEPOINT來對當前點進行標記,然后可以通過回滾操作來退回到指定的點,這樣我們可以定義多個不同的SAVEPOINT來實現不同節點的回滾;注意,如果SAVEPOINT定義了相同的名字,那么后面的會覆蓋掉前面的定義;對於不再需要的SAVEPOINT可以通過RELEASE SAVEPOINT命令來刪除。
會話1 | 會話2 |
---|---|
查詢第11條記錄,結果為空 mysql> select * from actor where actor_id=11; |
第11條記錄同樣為空 mysql> select * from actor where actor_id = 11; |
啟動一個事務,向actor表中插入數據 mysql> start transaction; mysql> insert into actor values(11,'k','k'); |
|
可以查詢到剛剛插入的記錄 mysql> select * from actor where actor_id=11; |
無法查詢到會話1剛插入的記錄 mysql> select * from actor where actor_id = 11; |
定義savepoint,名為test mysql> savepoint test; 繼續插入一條數據 mysql> insert into actor values(12,'l','l'); |
|
可以查詢到插入的兩條記錄: mysql> select * from actor where actor_id=11 or actor_id = 12; |
依然無法查詢到結果: mysql> select * from actor where actor_id = 11 or actor_id = 12; |
回滾到剛才定義的savepoint點 mysql> rollback to savepoint test; |
|
此時只能找到第11條記錄,第12條已經被回滾了: mysql> select * from actor where actor_id=11 or actor_id = 12; |
依然無結果: mysql> select * from actor where actor_id = 11 or actor_id = 12; |
提交: mysql> commit; |
|
只能查到第11條記錄 mysql> select * from actor where actor_id=11 or actor_id = 12; |
同樣只能查到第11條記錄 mysql> select * from actor where actor_id = 11 or actor_id = 12; |
三、分布式事務的使用
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'; 分支事務1在actor表中插入一條記錄: mysql> insert into actor values(13,'l','l'); |
在數據庫mydb1中啟動分布式事務‘test’的另外一個分支事務 mysql> xa start 'test','mydb1'; 分支事務2在表mydb_test中插入兩條記錄 mysql> insert into mydb_test values(1,'test1'),(2,'test2'); |
對分支事務1進行第一階段提交,進入prepare狀態: mysql> xa end 'test','test1'; mysql> xa prepare 'test','test1'; |
對分支事務2進行第一階段提交,進入prepare狀態: mysql> xa end 'test','mydb1'; mysql> xa prepare 'test','mydb1'; |
查看出於prepare狀態的事務 mysql> xa recover \G |
查看出於prepare狀態的事務 mysql> xa recover \G; |
兩個事務都進入准備提交階段,如果提交遇到錯誤,應該回滾所有的分支以確保分布式任務的正確 | |
提交分支事務1: mysql> xa commit 'test','test1'; |
提交分支事務2: mysql> xa commit 'test','mydb1'; |
3.3 存在的問題
MySQL5.7.7版本之前,它支持的分布式是有缺陷的,具體表現在:
- 已經prepare的事務,在客戶端異常退出或者服務宕機的時候,2PC的事務會被回滾;
- 在服務器故障重啟提交后,相應的Binlog被丟失。
但是在5.7.7版本之后,這個bug被修復了,后面使用MySQL的分布式事務就有了較好的支持。
作者:Coding___Man
來源:CSDN
原文:https://blog.csdn.net/Coding___Man/article/details/85234561
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!