自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

MySQL:Innodb 唯一索引出現(xiàn)重復(fù)值的場(chǎng)景分析

數(shù)據(jù)庫(kù)
當(dāng)我們進(jìn)行數(shù)據(jù)插入的時(shí)候,對(duì)于唯一索引,實(shí)際上大概會(huì)經(jīng)歷數(shù)據(jù)查找,唯一性檢查、數(shù)據(jù)插入三個(gè)階段。

最近遇到類似案例,這里將可能出現(xiàn)這種情況的2個(gè)場(chǎng)景描述一下,其中一種情況在翻看老葉的公眾號(hào)有類似文章,如下,

故障案例:MySQL唯一索引有重復(fù)值,官方卻說(shuō)This is not a bug

我們分別描述。

場(chǎng)景1 unique_checks = 0

1.原理

當(dāng)我們進(jìn)行數(shù)據(jù)插入的時(shí)候,對(duì)于唯一索引,實(shí)際上大概會(huì)經(jīng)歷數(shù)據(jù)查找,唯一性檢查、數(shù)據(jù)插入 3個(gè)階段。而對(duì)于普通索引來(lái)講如果page不在buffer pool中則可能在數(shù)據(jù)查找階段就會(huì)寫(xiě)入到ibuff,這種情況就等待后續(xù)的ibuff合并即可。

但是我們一旦設(shè)置了unique_checks=0,對(duì)于唯一索引(非主鍵)而言也可能走普通索引的方式,我們大概看看是如何改變的,首先根據(jù)設(shè)置,事務(wù)檢查唯一索引的標(biāo)記會(huì)設(shè)置為如下,

  trx->check_unique_secondary =
      !thd_test_options(thd, OPTION_RELAXED_UNIQUE_CHECKS);

然后在插入階段,row_ins_sec_index_entry_low函數(shù)首先會(huì)根據(jù)是否檢查唯一性將search_mode 設(shè)置上BTR_IGNORE_SEC_UNIQUE標(biāo)記,search_mode 的值很多,主要包含2類,A:讀寫(xiě)鎖模式/B:操作方式,他們各自占用不同的bit位。

if (!thr_get_trx(thr)->check_unique_secondary) {search_mode |= BTR_IGNORE_SEC_UNIQUE;}

接下來(lái)就是查找數(shù)據(jù)調(diào)用btr_cur_search_to_nth_level上層函數(shù),進(jìn)行數(shù)據(jù)定位,然后在其中判定,

case BTR_INSERT:
      btr_op = (latch_mode & BTR_IGNORE_SEC_UNIQUE)
                   ? BTR_INSERT_IGNORE_UNIQUE_OP
                   : BTR_INSERT_OP;
      break;

如果為insert且latch_mode帶有BTR_IGNORE_SEC_UNIQUE,設(shè)置btr_op為BTR_INSERT_IGNORE_UNIQUE_OP。

最后在判定是否能夠使用ibuf上,我們看到如下,

    if (btr_op != BTR_NO_OP && 
        ibuf_should_try(index, btr_op != BTR_INSERT_OP)) { //是否進(jìn)入 ibuf
      /* Try to buffer the operation if the leaf
      page is not in the buffer pool. */

      fetch = btr_op == BTR_DELETE_OP ? Page_fetch::IF_IN_POOL_OR_WATCH //這里和 WATCH和purge線程有光
                                      : Page_fetch::IF_IN_POOL; //bug page get gen 只看是否在buffer中
    }

而函數(shù)ibuf_should_try就是判定是否使用ibuf,一旦使用ibuf,當(dāng)然修改的相關(guān)page就不一定非要在buffer pool中,因此對(duì)于insert操作定義為Page_fetch::IF_IN_POOL,而函數(shù)ibuf_should_try主要包含如下判定:

  • A:開(kāi)啟了change buffer
  • B:不等于系統(tǒng)表空間
  • C:不能是聚集索引
  • D:不能處于export狀態(tài)下
  • E:insert操作不能是唯一索引
  • F:其他操作,唯一索引也可以使用ibuf,這里實(shí)際上就只剩下delete和ignore唯一性的insert了

而在底層修改操作實(shí)際上只有insert和delete操作,而這里滿足的是F條件因此insert操作的查找page動(dòng)作被標(biāo)記為Page_fetch::IF_IN_POOL,接下來(lái)buf_page_get_gen函數(shù)就不會(huì)再去訪問(wèn)物理磁盤了,這個(gè)時(shí)候可能返回的page為NULL,那就要走這個(gè)邏輯了:

  if (block == nullptr) { //如果block沒(méi)有在innodb buffer中進(jìn)行操作
...
    switch (btr_op) {
      case BTR_INSERT_OP:
      case BTR_INSERT_IGNORE_UNIQUE_OP: //注意這里
...
        if (ibuf_insert(IBUF_OP_INSERT, tuple, index, page_id, page_size,
                        cursor->thr)) {
          cursor->flag = BTR_CUR_INSERT_TO_IBUF;
          goto func_exit;
        }

也就是插入到ibuf中,那么我們可以想象,如果設(shè)置了unique_checks=0,這個(gè)時(shí)候如果重復(fù)的數(shù)據(jù)在磁盤上(因?yàn)閕nnodb buffer查詢不到page返回NULL),則會(huì)將接下來(lái)的數(shù)據(jù)本該重復(fù)的數(shù)據(jù)插入到ibuf,而不會(huì)去檢測(cè)重復(fù)值。然后等到需要讀取這個(gè)page到buffer pool的時(shí)候比如select,那就需要做ibuf的合并,合并后重復(fù)的數(shù)據(jù)就出現(xiàn)了。

2.測(cè)試

測(cè)試可以根據(jù)老葉公眾號(hào)的方式測(cè)試,主體思想就是做一個(gè)大一點(diǎn)的表,然后重啟數(shù)據(jù)庫(kù),并且不要開(kāi)啟啟動(dòng)時(shí)加載page到buffer pool,下面是我測(cè)試的結(jié)果:

這里b列是一個(gè)唯一索引,我們看到了第二查詢出現(xiàn)了2個(gè)相同的值。

3.其他和總結(jié)

當(dāng)出現(xiàn)這種情況的時(shí)候可以看到,第一個(gè)查詢只出現(xiàn)了一行,這看起來(lái)好像是對(duì)的,但是實(shí)際上索引上有2行不同的值,對(duì)于唯一索引來(lái)講如果訪問(wèn)到一行值,訪問(wèn)就會(huì)停止,因此出現(xiàn)了這種情況,看起來(lái)也是比較奇特。 因此我們?cè)诳紤]使用unique_checks=0加速導(dǎo)入數(shù)據(jù)的時(shí)候需要特別注意一下這個(gè)問(wèn)題,除非能夠保證數(shù)據(jù)都是唯一的否則不建議設(shè)置,現(xiàn)在我們知道實(shí)際上加速就是讓唯一索引也能夠使用ibuf這個(gè)特性,這里我們?cè)賮?lái)會(huì)看一下官方的這句話

For big tables, this saves a lot of disk I/O because InnoDB can use its change buffer to write secondary index records in a batch. Be certain that the data contains no duplicate keys.

很顯然和我們分析一致。

場(chǎng)景2 RR隔離級(jí)別相關(guān)

這個(gè)地方主要和隔離級(jí)別有關(guān)了,雖然提出這個(gè)BUG的時(shí)間有點(diǎn)久了,但是這不是BUG,并且8.0也能重現(xiàn),如下, https://bugs.mysql.com/bug.php?id=69979

重現(xiàn)如下:

建表和插入數(shù)據(jù)
create table testuniq(id int primary key,a int unique key);
 insert into testuniq values (10, 100), (20, 200);
mysql> select * from testuniq;
+----+------+
| id | a    |
+----+------+
| 10 |  100 |
| 20 |  200 |
+----+------+

TRX1

TRX2

1.begin;


2.select * from testuniq;



3.update testuniq set a=300 where id=10;

4.update testuniq set a=100 where id=20;


5.select * from testuniq;


完成第四步的時(shí)候數(shù)據(jù)就是:

mysql> select * from testuniq;
+----+------+
| id | a    |
+----+------+
| 10 |  100 |
| 20 |  100 |
+----+------+

可以看到唯一索引出現(xiàn)了重復(fù)值,對(duì)于這個(gè)問(wèn)題,只要不阻止第4步的update testuniq set a=100 where id=20操作按照原理上來(lái)講就會(huì)出現(xiàn)重復(fù)值,因?yàn)镽R有一個(gè)read view在begin開(kāi)始后第一個(gè)select語(yǔ)句后一直存在,而update屬于當(dāng)前讀訪問(wèn)的當(dāng)前記錄已經(jīng)被修改了,因此第4步并沒(méi)有訪問(wèn)歷史記錄,因此update通過(guò),最終出現(xiàn)這種現(xiàn)象。同時(shí)在BUG中也詳細(xì)描述了這是符合設(shè)計(jì)的PG也是類似的結(jié)果,可以自行參考。

責(zé)任編輯:趙寧寧 來(lái)源: MySQL學(xué)習(xí)
相關(guān)推薦

2023-02-26 00:00:06

MySQL索引故障

2016-08-05 14:33:19

MySQL索引數(shù)據(jù)庫(kù)

2024-08-19 09:43:00

2024-05-24 09:29:28

2022-08-04 08:22:49

MySQL索引

2018-09-14 09:12:00

數(shù)據(jù)庫(kù)MySQL索引約束

2018-09-16 23:14:18

MySQL索引約束主鍵

2011-08-18 11:18:25

Oracle唯一約束唯一索引

2023-01-03 07:44:53

MySQL查詢重復(fù)

2015-05-20 13:48:26

MySQL索引

2021-04-12 10:52:10

InnoDB索引數(shù)據(jù)庫(kù)

2010-10-12 14:40:03

mysql索引

2022-01-27 11:02:04

索引數(shù)據(jù)存儲(chǔ)

2010-11-25 14:40:27

MySQL查詢

2023-02-10 10:14:59

普通索引唯一索引

2024-04-19 13:57:30

索引數(shù)據(jù)庫(kù)查詢

2011-06-15 18:59:16

算法SEO

2021-09-01 08:58:15

項(xiàng)目 UTFailed

2010-03-02 16:50:34

WCF返回值

2020-12-09 10:10:24

MySQL數(shù)據(jù)庫(kù)算法
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)