Mysql InnoDB 系列 關於一致讀

2021-10-20 10:50:14 字數 4889 閱讀 1181

系列文章:

【mysql-innodb 系列】innodb 架構

【mysql-innodb 系列】鎖

【mysql-innodb 系列】事務模型

一致讀(consistent read),在《mysql技術內幕 第二版》中稱為一致性非鎖定讀(consistent nonlocking read),是指innodb使用多版本控制(multi versioning)向查詢提供資料庫在某個時間點的快照。

一致讀查詢能夠看到在該時間點之前提交的事務所做的更改,而不會看到稍後或未提交的事務所做的更改。此規則的例外情況是,查詢可以看到同一事務中早期語句所做的更改。這個例外導致了以下異常:如果更新表中的某些行,select會看到更新行的最新版本,但也可能會看到任何行的舊版本。如果其他會話同時更新同乙個表,則這個異常意味著你可能會看到該錶處於資料庫中從未存在過的狀態。

如果事務隔離級別是可重複讀(預設的隔離級別),同乙個事務中的所有一致讀讀取的都是由事務中第乙個一致讀建立的快照。可以通過提交當前事務並在提交後發出新的查詢,來為你的查詢獲取更新的快照。

讀已提交隔離級別下,乙個事務中的每個一致讀都會設定並讀取它自己的新快照。

一致讀是innodb在讀已提交 和 可重複讀 隔離級別下處理select語句的預設模式。一致讀不會為它訪問的表上設定任和鎖,因此,其他會話可以在對錶執行一致讀取的同時自由修改這些表。

假設你正執行在預設的可重複讀隔離級別下。當您發出一致的read(即普通的select語句)時,innodb會給事務乙個時間點,根據這個時間點,您的查詢可以看到資料庫。如果另乙個事務刪除一行並在分配了時間點後提交,則不會將該行視為已刪除。插入和更新的處理方式類似。

注:資料庫狀態的快照應用於事務中的select語句,而不一定應用於dml語句。如果插入或修改某些行,然後提交該事務,則從另乙個併發可重複讀取事務發出的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查詢 或 啟動一致性快照的事務。這被稱為多版本併發控制。

在下面的示例中,會話a僅在b提交了insert並且a也提交了insert時才看到b插入的行,因此時間點提前到b提交之後。

session a session b

set autocommit=0; set autocommit=0;

time

| select * from t;

| empty set

| insert into t values (1, 2);

|v select * from t;

empty set

commit;

select * from t;

empty set

commit;

select * from t;

---------------------

| 1 | 2 |

---------------------

如果你想要看到資料庫的「最新」狀態,可以使用讀已提交隔離級別 或 鎖定讀:

select * from t for share;
讀已提交 隔離級別下,事務中的每個一致讀設定並讀取它自己的新快照。通過for share,將會發生鎖定讀:select語句被阻塞,直到包含最新行的事務結束

一致讀在特定的ddl語句下不會生效:

1、一致讀不適用於drop table語句,因為mysql無法使用乙個已經drop掉的表,innodb會銷毀這張表。

2、一致讀不適用於alter table操作,alter table會生成原始表的乙個臨時副本,並在臨時副本建立後刪除原始表。在事務中重新發出一致讀取時,新錶中的行不可見,因為在獲取事務快照時,這些行不存在。在這種情況下,事務會返回錯誤:er_table_def_changed,「表定義發生變化,請重試事務」。

select語句讀的型別各不相同,例如insert into...select, update...(select),以及create table ... select,這些未指定for update 或 for share:

1、預設情況下,innodb對這些語句使用更強的鎖,select部分的作用類似於讀已提交,其中每個一致讀(即使在同一事務中)都設定並讀取自己的新快照。

2、要在這種情況下執行非鎖定讀取,請將事務的隔離級別設定為 讀未提交 或讀已提交,以避免對從所選錶讀取的行設定鎖。

如前面所說,read committed事務隔離級別下,一致讀總是讀取行的最新版本,如果行被鎖定,就讀取該行版本的最新的快照。乙個示例如下:

1、事務隔離級別設定/確認為讀已提交

mysql> select @@tx_isolation;

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

| @@tx_isolation |

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

| read-committed |

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

2、建立表,表t是一張示例表,建表語句:

create table `t` (

`i` int(11) not null,

primary key (`i`)

) engine=innodb default charset=utf8;

//插入1條資料

insert into t values(1);

3、開啟會話a,begin開啟事務:

mysql> begin;

query ok, 0 rows affected (0.01 sec)

mysql> select * from t where i=1;

+---+

| i |

+---+

| 1 |

+---+

1 row in set (0.00 sec)

mysql> select * from t where i=5;

empty set (0.01 sec)

由於只有一條記錄1,所以查詢i=5時返回為空。

4、開啟新的會話b,begin開啟事務,執行更新動作,但先不提交:

mysql> begin

-> ;

query ok, 0 rows affected (0.00 sec)

mysql> update t set i=5 where i=1;

query ok, 1 row affected (0.02 sec)

rows matched: 1 changed: 1 warnings: 0

mysql> select * from t where i=1;

empty set (0.00 sec)

mysql> select * from t where i=5;

+---+

| i |

+---+

| 5 |

+---+

1 row in set (0.00 sec)

5、回到會話a,查詢i=5:

mysql> select * from t where i=5;

empty set (0.01 sec)

由於b事務並未提交,而當前隔離級別為讀已提交,所以查不到i=5的記錄是符合預期的、

6、會話b提交事務:

mysql> commit;

query ok, 0 rows affected (0.01 sec)

7、在回到會話a,查詢i=5記錄:

mysql> select * from t where i=5;

+---+

| i |

+---+

| 5 |

+---+

1 row in set (0.00 sec)

由此可見,對於read committed事務隔離級別,從資料庫理論來看,違背了事務acid中的隔離性(i)。這也是我們在前面資料庫事務模型文章中,對可重複讀 隔離級別的乙個例項證明。

關於 Mysql innodb的索引

關於innodb的索引,可以分為聚簇索引,輔助索引,都是以b tree 為底層資料結構。聚簇索引 只是資料的儲存方法。以主鍵為key,如果表中沒有主鍵,則會選擇乙個有唯一索引的列作為key,如果都沒有,innodb會為我們建立乙個唯一列作為key。所有的資料都存在葉子節點上,並且是按順序儲存的。如果...

MySQL innoDB效能優化

起因 有乙個innodb引擎的表table,在乙個大概3000次的foreach迴圈中執行 insert into table columna,columnb values valuea,valueb 結果居然超出了60s的php執行限制 當然這個限制可以在php.ini中修改 讓我很不解為何插入效...

mysql innodb 效能優化

預設情況下,innodb的引數設定的非常小,在生產環境中遠遠不夠用 比如最重要的兩個引數 innodb buffer pool size 預設是8m innodb flush logs at trx commit 預設設定的是1 也就是同步重新整理log 可以這麼理解 innodb buffer p...