主博客:
【MySQL精通之路】InnoDB(7)-鎖和事務模型-CSDN博客
上一篇:
【MySQL精通之路】InnoDB(7)-鎖和事務模型(1)-鎖-CSDN博客
下一篇:
目錄
1.事務隔離級別
2.1 可重復讀
2.2 讀已提交
2.3 讀取未提交
2.4 序列化讀
2.自動提交、提交和回滾
2.1 將DML操作以事務分組
2.2 客戶端語言的事務
3.無鎖快照讀
4.鎖讀取
4.1 SELECT ... FOR SHARE
4.2 SELECT ... FOR UPDATE
4.3 加鎖讀示例
5 使用NOWAIT和SKIP LOCKED實現加鎖讀并發
InnoDB事務模型旨在將多版本數據庫的最佳屬性(MVCC)與傳統的兩階段鎖相結合。
InnoDB支持行鎖,默認情況下以Oracle的風格作為不使用鎖,快照讀的方式來運行查詢。
InnoDB中的鎖信息被高效的存儲著,因此不需要擴增鎖空間。
通常,允許幾個用戶鎖定InnoDB表中的每一行,或行的任何隨機子集,而不會導致InnoDB內存耗盡。
1.事務隔離級別
事務隔離是數據庫處理的基礎之一。隔離是縮寫詞ACID中的I(Isolation);隔離級別是在多個事務同時進行更改和執行查詢時,對性能與結果的可靠性、一致性和可再現性之間的平衡進行微調的設置。
InnoDB提供了SQL:1992標準所描述的所有四個事務隔離級別:
READ UNCOMITTED:讀未提交
READ COMMITTED:讀提交
REPEATABLE READ:可重復讀
SERIALIZABLE:串行化
InnoDB的默認隔離級別是REPEATABLE READ。
用戶可以使用SET TRANSACTION語句更改單個會話或所有后續連接的隔離級別。
要為所有連接設置服務器的默認隔離級別,請在命令行或選項文件中使用
--transaction-isolation
選項。有關隔離級別和級別設置語法的詳細信息,請參閱“SET TRANSACTION語句”。
InnoDB使用不同的鎖策略支持這里描述的每個事務隔離級別。、
對于ACID合規性很重要的關鍵數據上的操作,您可以使用默認的REPEATABLE READ級別強制實現高度一致性。
或者,您可以使用READ COMMITTED甚至READ UNCOMITTED放寬一致性規則,在批量報告等情況下,精確的一致性和可重復的結果不如最小化鎖定開銷重要。
SERIALIZABLE執行比REPEATABLE READ更嚴格的規則,主要用于特殊情況,如XA事務以及并發和死鎖問題的故障排除。
以下列表描述MySQL如何支持不同的事務級別。該列表從最常用的級別到最不常用的級別。
2.1 可重復讀
這是InnoDB的默認隔離級別。
同一事務中的一致讀取由第一次讀取建立的快照。
這意味著,如果在同一事務中發出多個純(非鎖定)SELECT語句,這些SELECT語句彼此之間也是一致的。參見第17.7.2.3節“一致非鎖定讀取”。
對于鎖定讀取(SELECT ... FOR UPDATE or FOR SHARE)、UPDATE和DELETE語句,鎖取決于該語句是使用具有唯一搜索條件的唯一索引,還是使用范圍類型的搜索條件。
2.1.1 唯一索引下的可重復讀
對于具有唯一搜索條件的唯一索引,InnoDB只鎖定找到的索引記錄,而不鎖定之前的間隙。
2.1.2 其他索引下的可重復讀
對于其他搜索條件,InnoDB會鎖定掃描索引范圍,使用間隙鎖或臨鍵鎖來阻止其他會話插入到范圍覆蓋的間隙中。有關間隙鎖和臨鍵鎖的信息,請參閱“InnoDB鎖”。
【MySQL精通之路】Innodb-鎖和事務-CSDN博客
2.2 讀已提交
即使在同一事務中,每次一致讀取都會設置和讀取自己的新快照。
有關一致讀取的信息,請參閱“一致非鎖定讀取”。
對于鎖定讀取(SELECT with FOR UPDATE or FOR SHARE)、UPDATE語句和DELETE語句
InnoDB只鎖定索引記錄,而不鎖定它們之前的間隙,因此允許在鎖定行之后自由插入新記錄。
間隙鎖僅用于外鍵約束檢查和重復key檢查。
由于間隙鎖被禁用,可能會出現幻行問題,因為其他會話可以將新行插入間隙中。
有關幻行的信息,請參閱“幻行”。
READ COMMITTED隔離級別僅支持基于行的binlog。
如果將READ COMMITTED與binlog_format=MIXED一起使用,則服務器會自動使用基于行的日志記錄。
使用READ COMMITTED還有其他效果:
對于UPDATE或DELETE語句,InnoDB只為更新或刪除的行保留鎖。
MySQL評估WHERE條件后,將釋放不匹配行的記錄鎖。這大大降低了死鎖的概率,但它們仍然可能發生。
對于UPDATE語句,如果一行已經被鎖定,InnoDB會執行“半一致”讀取,將最新提交的版本返回給MySQL,以便MySQL可以確定該行是否符合UPDATE的WHERE條件。如果行匹配(必須更新),MySQL會再次讀取該行,這一次InnoDB要么鎖定它,要么等待鎖定。
考慮以下示例,從下表開始:
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;
在這種情況下,表沒有索引,因此搜索和索引掃描使用隱藏的聚集索引來鎖定記錄
(請參見“聚集索引和輔助索引”),而不是索引列。
【MySQL精通之路】Innodb-索引-CSDN博客
假設一個會話使用以下語句執行UPDATE:
# Session A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;
還假設第二個會話通過在第一個會話之后執行這些語句來執行UPDATE:
# Session B
UPDATE t SET b = 4 WHERE b = 2;
當InnoDB執行每個UPDATE時,它首先為每一行獲取一個互斥鎖,然后決定是否修改它。
如果InnoDB不修改行,它就會釋放鎖。否則,InnoDB將保留鎖,直到事務結束。這會對事務處理產生以下影響。
當使用默認的REPEATABLE READ隔離級別時,第一個UPDATE會在它讀取的每一行上獲取一個x鎖,并且不會釋放任何一個:
x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock
第二個UPDATE在嘗試獲取任何鎖時立即阻塞(因為第一個更新在所有行上都保留了鎖),直到第一個UPDATE提交或回滾才繼續:
x-lock(1,2); block and wait for first UPDATE to commit or roll back
如果改為使用READ COMMITTED,則第一個UPDATE會在其讀取的每一行上獲取一個x鎖,并釋放未修改的行的x鎖:
x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)
對于第二個UPDATE,InnoDB進行“半一致”讀取,將讀取的每一行的最新提交版本返回給MySQL,以便MySQL確定該行是否符合UPDATE的WHERE條件:
x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock
但是,如果WHERE條件包括索引列,并且InnoDB使用索引,則在獲取和保留記錄鎖時只考慮索引列。在下面的示例中,第一個UPDATE在b=2的每一行上獲取并保留一個x鎖。第二個UPDATE在嘗試獲取相同記錄上的x鎖時會阻塞,因為它還使用了在列b上定義的索引。
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;# Session A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;# Session B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
READ COMMITTED隔離級別可以在啟動時設置,也可以在運行時更改。在運行時,它可以為所有會話全局設置,也可以為每個會話單獨設置。
2.3 讀取未提交
SELECT語句以無鎖方式執行,但可能會使用行的早期版本。
因此,使用這種隔離級別,這樣的讀,多次讀的數據是不一致的。這也被稱為臟讀。
此外,此隔離級別的工作方式與READ COMMITTED類似。
2.4 序列化讀
這個級別類似于REPEATABLE READ
但InnoDB隱式地將所有純SELECT語句轉換為SELECT。。。FOR SHARE
(如果禁用了自動提交)。
如果啟用了自動提交,SELECT就是它自己的事務。
因此,它是只讀的,如果作為一致(非鎖定)讀取執行,則可以序列化,并且不需要為其他事務阻塞。(如果其他事務已經修改了所選行,要強制阻塞SELECT,請禁用自動提交。)
注意:
從MySQL 8.0.22開始,從MySQL 權限表 讀取數據(通過聯表查詢或子查詢)但不修改數據的DML操作不會獲取MySQL 權限表上的讀鎖,無論隔離級別如何。有關詳細信息,請參閱權限表并發性。
2.自動提交、提交和回滾
在InnoDB中,所有用戶活動都發生在事務內部。
如果啟用了自動提交模式,則每條SQL語句將自己形成一個事務。
默認情況下,MySQL在啟用自動提交的情況下為每個新連接啟動會話,因此,如果每條SQL語句沒有返回錯誤,MySQL會在該語句之后進行提交。
如果語句返回錯誤,則提交或回滾行為取決于錯誤。
參見“InnoDB錯誤處理”。
啟用了自動提交的會話可以執行多語句事務,方法是以顯式START TRANSACTION或BEGIN語句開始,然后以COMMIT或ROLLBACK語句結束。
參見“START TRANSACTION、COMMIT和ROLLBACK語句”。
如果在SET autocommit=0的會話中禁用了自動提交模式,則該會話始終打開一個事務。
COMMIT或ROLLBACK語句結束當前事務,并啟動新事務。
如果禁用了自動提交的會話在沒有顯式提交最終事務的情況下結束,MySQL將回滾該事務。
有些語句隱式地結束事務,就好像在執行該語句之前執行了COMMIT一樣。
有關詳細信息,請參閱“導致隱式提交的聲明”。
COMMIT意味著在當前事務中所做的更改是永久性的,并且對其他會話可見。
另一方面,ROLLBACK語句取消當前事務所做的所有修改。
COMMIT和ROLLBACK都會釋放在當前事務期間設置的所有InnoDB鎖。
Grouping DML Operations with Transactions
2.1 將DML操作以事務分組
????????默認情況下,與MySQL服務器的連接從啟用自動提交模式開始,該模式會在您執行每一條SQL語句時自動提交。
????????如果您有其他數據庫系統的經驗,這種操作模式可能會不熟悉,因為在其他數據庫系統中,標準做法是發布一系列DML語句,然后將它們一起提交或回滾。
????????要使用多個語句事務,請使用SQL語句SET autocommit=0關閉自動提交,并根據需要使用COMMIT或ROLLBACK結束每個事務。
????????若要保留自動提交,請以START TRANSACTION開始每個事務,然后以COMMIT或ROLLBACK結束。以下示例顯示了兩個事務。第一個是COMMIT;第二個ROLLBACK。
$> mysql test
mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do a transaction with autocommit turned on.
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do another transaction with autocommit turned off.
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- Now we undo those last 2 inserts and the delete.
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a | b |
+------+--------+
| 10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
2.2 客戶端語言的事務
在PHP、PerlDBI、JDBC、ODBC等API或MySQL的標準C調用接口中,您可以像SELECT或INSERT等任何其他SQL語句一樣,將COMMIT等事務控制語句作為字符串發送到MySQL服務器。一些API還提供單獨的特殊事務提交和回滾功能或方法。
3.無鎖快照讀
快照讀意味著InnoDB使用MVCC在某個時間設置數據庫的快照。
此事務的查詢會看到在此時間點之前提交的事務所做的更改,而不會看到以后或未提交的事務進行的更改。
此規則的例外情況是,查詢會看到同一事務中前面的語句所做的更改。
此時會導致以下異常:
如果更新表中的某些行,SELECT會看到更新行的最新版本,但也可能會看到任何行的舊版本。
如果其他會話同時更新同一個表,則異常意味著您可能會看到該表從未存在過的狀態。
PS:上面說的是讀提交。
如果事務隔離級別為REPEATABLE READ(默認級別),則同一事務中的所有快照讀都會讀取該事務中第一次的快照讀。通過提交當前事務,然后發出新的查詢,可以為查詢獲取更新的快照。
使用READ COMMITTED隔離級別,事務中的每個快照讀都會設置并讀取自己的新快照。
快照讀是InnoDB在?READ COMMITTED和?REPEATABLE READ隔離級別中處理SELECT語句的默認模式。
一致性讀取不會對其訪問的表設置任何鎖,因此,在對表執行一致性讀取的同時,其他會話可以自由修改這些表。
假設您在默認的REPEATABLE READ隔離級別下運行。當您發出快照讀(即普通的SELECT語句)時,InnoDB會為您的事務提供一個時間點,根據該時間點您的查詢可以看到數據庫。
如果另一個事務刪除了一行并在指定時間點后提交,則不會認為該行已被刪除。插入和更新的處理方式類似。
注意:
數據庫狀態的快照應用于事務中的SELECT語句,而不一定應用于DML語句。
如果插入或修改某些行,然后提交該事務,則從另一個并發的REPEATABLE READ事務發出的DELETE或UPDATE語句可能會影響那些剛剛提交的行,即使會話無法查詢它們。如果事務確實更新或刪除了由其他事務提交的行,則這些更改對當前事務可見。
例如,您可能會遇到以下情況:
SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz'; -- Returns 0: no rows match. DELETE FROM t1 WHERE c1 = 'xyz'; -- Deletes several rows recently committed by other transaction.SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc'; -- Returns 0: no rows match. UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc'; -- Affects 10 rows: another txn just committed 10 rows with 'abc' values. SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba'; -- Returns 10: this txn can now see the rows it just updated.
您可以通過執行另一個SELECT或START TRANSACTION WITH CONSISTENT SNAPSHOT且提交事務來提前時間點。
這被稱為多版本并發控制。
在下面的示例中,會話A只有在B提交了插入并且A也提交了插入時才看到由B插入的行,因此時間點提前到B的提交之后。
Session A Session BSET autocommit=0; SET autocommit=0;
time
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;empty setCOMMIT;SELECT * FROM t;empty setCOMMIT;SELECT * FROM t;---------------------| 1 | 2 |---------------------
如果您想查看數據庫的“最新”狀態,請使用READ COMMITTED隔離級別 或 鎖讀取:
SELECT * FROM t FOR SHARE;
使用READ COMMITTED隔離級別,事務中的每個快照讀都會設置并讀取自己的新快照。
對于FOR SHARE,將發生鎖定讀取:SELECT將阻塞,直到包含最新行的事務結束
(請參閱“鎖讀取”)
快照讀對某些DDL語句無效:
快照讀不適用于DROP TABLE,因為MySQL無法使用已刪除的表,而InnoDB會破壞該表。
快照讀不適用于生成原始表的臨時副本并在生成臨時副本時刪除原始表的ALTER TABLE操作。在事務中重新發出一致讀取時,新表中的行不可見,因為在獲取事務的快照時這些行不存在。在這種情況下,事務返回錯誤:ER_TABLE_DEF_CHANGED,“表定義已更改,請重試事務”。
快照讀的類型隨著查詢語句變化而變化,比如
INSERT INTO ... SELECT, UPDATE ... (SELECT)
CREATE TABLE ... SELECT 不指定 FOR UPDATE 或 FOR SHARE:
默認情況下,InnoDB對這些語句使用更強的鎖,SELECT部分的行為類似于READ COMMITTED,其中每個快照讀即使在同一事務中,也會設置和讀取自己的新快照。
在這種情況下,要執行無鎖快照讀,請將事務的隔離級別設置為READ UNCOMMITTED或READ COMMITTED,以避免設置行鎖。
4.鎖讀取
如果查詢數據時在同一事務中插入或更新相關數據,則常規SELECT語句無法提供足夠的保護。
其他事務可以更新或刪除您剛才查詢的同一行。
InnoDB支持兩種類型的鎖定讀取,提供額外的安全性:
4.1 SELECT ... FOR SHARE
在讀取的任何行上設置共享模式鎖定。其他會話可以讀取這些行,但在事務提交之前不能修改它們。如果這些行中的任何一行被另一個尚未提交的事務更改,則查詢將等待該事務結束,然后使用最新的值。
注意:
SELECT ... FOR SHARE 是 SELECT ... LOCK IN SHARE MODE的替代品。但LOCK IN SHARE MODE任然可用。這些語句是等效的。
但是,FOR SHARE支持OF table_name、NOWAIT和SKIP LOCKED選項。
請參閱
使用NOWAIT和SKIP LOCKED鎖定讀取并發。
MySQL 8.0.22之前,SELECT。。。FOR SHARE需要SELECT權限和至少一個DELETE、LOCK TABLES或UPDATE權限。
從MySQL 8.0.22開始,只需要SELECT權限。
從MySQL 8.0.22,SELECT。。。FOR SHARE語句不會獲取MySQL 權限表上的讀鎖。
有關詳細信息,請參閱權限表并發性。
4.2 SELECT ... FOR UPDATE
對于查詢到的行記錄,會鎖住行和任何關聯的索引數據,就像為這些行發出UPDATE語句一樣。
其他事務被阻止更新這些行、執行SELECT。。。FOR SHARE,或讀取某些事務隔離級別的數據。
快照讀將忽略在快照中記錄上的任何鎖。(記錄的舊版本無法鎖定;它們是通過在記錄的內存副本上應用撤消日志來重建的。)
SELECT ... FOR UPDATE需要SELECT權限和至少一個DELETE、LOCK TABLES或UPDATE權限。
當事務被提交或回滾時,FOR SHARE和FOR UPDATE查詢設置的所有鎖都會被釋放。
注意:
只有在禁用自動提交時(通過START transaction開始事務或將自動提交設置為0),才可以鎖定讀取。
外部語句中的加鎖讀不會鎖定嵌套子查詢中表的行,除非在子查詢中也指定了加鎖讀子句。
例如,以下語句不會鎖定表t2中的行。
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
要鎖定表t2中的行,請向子查詢添加一個鎖定讀取子句:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
4.3 加鎖讀示例
假設要將新行插入到表的child表中,并確保該child行在parent表中具有父行。您的應用程序代碼可以確保整個操作序列中的引用完整性。
首先,使用一快照讀來查詢表PARENT,并驗證父行是否存在。您能安全地將子行插入到表child中嗎?
不可以,因為其他會話可能會在SELECT和INSERT之間刪除父行,而您不會意識到這一點。
要避免此潛在問題,請使用FOR SHARE執行查詢:
SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;
?在FOR SHARE查詢返回父“Jones”之后,可以安全地將子記錄添加到child表并提交事務。
任何試圖在PARENT表中的對應行中獲取獨占鎖的事務都會等待,直到完成,也就是說,直到所有表中的數據處于一致狀態。
例如,考慮表CHILD_CODE中的一個整數計數器字段,該字段用于為添加到表CHILD的每個子項分配唯一標識符。不要使用一致讀取或共享模式讀取來讀取計數器的當前值,因為數據庫的兩個用戶可能會看到計數器的相同值,如果兩個事務試圖將具有相同標識符的行添加到CHILD表中,則會出現重復鍵錯誤。
這里,FOR SHARE不是一個好的解決方案,因為如果兩個用戶同時讀取計數器,那么當它試圖更新計數器時,至少其中一個用戶會陷入死鎖。
要實現計數器的讀取和遞增,首先使用FOR UPDATE執行計數器的鎖定讀取,然后遞增計數器。例如:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
SELECT ... FOR UPDATE讀取最新的可用數據,對它讀取的每一行設置獨占鎖。因此,它設置了UPDATE語句將在行上設置的相同的鎖。
前面的描述只是SELECT。。。FOR UPDATE有效。
在MySQL中,生成唯一標識符的具體任務實際上只需對表進行一次訪問即可完成:
UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
?SELECT語句僅檢索標識符信息(特定于當前連接)。它不訪問任何表。
5 使用NOWAIT和SKIP LOCKED實現加鎖讀并發
??????? 如果某一行被事務鎖定,則SELECT ... FOR UPDATE 或SELECT ... FOR SHARE事務必須等待直到其他釋放行鎖。此行為防止其他更新或刪除行事務。
但是,如果您希望查詢到行被鎖定時立即返回,或者如果可以從結果集中排除鎖定的行,則無需等待行鎖定被釋放。
為了避免等待其他事務釋放行鎖,NOWAIT和SKIP LOCKED選項可以與SELECT ... FOR UPDATE 或SELECT ... FOR SHARE 加鎖讀語句一起使用。
NOWAIT
使用NOWAIT的加鎖讀從不等待獲取行鎖。查詢會立即執行,如果請求的行被鎖定,則會失敗并返回錯誤。
SKIP LOCKED
使用SKIP LOCKED的加鎖讀從不等待獲取行鎖。查詢立即執行,從結果集中刪除鎖定的行。
NOWAIT和SKIP LOCKED僅適用于行級鎖。
使用NOWAIT或SKIP LOCKED的語句對于基于語句的復制是不安全的。
以下示例演示NOWAIT和SKIP LOCKED。
會話1啟動一個事務,該事務對單個記錄加行鎖。
會話2嘗試使用NOWAIT選項對同一記錄進行加鎖讀。
因為請求的行被會話1鎖定,所以加鎖讀會立即返回并返回一個錯誤。
在會話3中,使用SKIP LOCKED進行的加鎖讀返回請求的行,但會話1鎖定的行除外。
# Session 1:mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;mysql> INSERT INTO t (i) VALUES(1),(2),(3);mysql> START TRANSACTION;mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+# Session 2:mysql> START TRANSACTION;mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.# Session 3:mysql> START TRANSACTION;mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+