精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

MySQL 自適應(yīng)哈希索引—構(gòu)造

數(shù)據(jù)庫(kù) MySQL
AHI 構(gòu)造流程的前三步都是在判斷是否滿(mǎn)足某些條件,這些條件的范圍從大到小。先是索引級(jí)別,判斷索引被命中的次數(shù)。然后,是索引級(jí)別的構(gòu)造信息計(jì)數(shù)。

曾經(jīng)優(yōu)化慢查詢(xún)時(shí),經(jīng)常在日志中看到 truncate,當(dāng)時(shí)一直疑惑 truncate 為什么會(huì)慢。

轉(zhuǎn)到數(shù)據(jù)庫(kù)方向之后,又碰到過(guò)幾次 truncate 執(zhí)行時(shí)間過(guò)長(zhǎng),導(dǎo)致 MySQL 短暫卡住的問(wèn)題。

經(jīng)過(guò)源碼分析和同事測(cè)試驗(yàn)證,發(fā)現(xiàn)這幾次的問(wèn)題都跟自適應(yīng)哈希索引有關(guān),所以,深入研究下自適應(yīng)哈希索引就很有必要了。

自適應(yīng)哈希索引大概會(huì)有 3 篇文章。第 1 篇,我們來(lái)看看自適應(yīng)哈希索引的構(gòu)造過(guò)程。

本文基于 MySQL 8.0.32 源碼,存儲(chǔ)引擎為 InnoDB。

1、準(zhǔn)備工作

創(chuàng)建測(cè)試表:

CREATE TABLE `t1` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `i1` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_i1` (`i1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

插入測(cè)試數(shù)據(jù):

INSERT INTO `t1`(`id`, `i1`)
VALUES (10, 101), (20, 201), (30, 301);

示例 SQL:

SELECT * FROM `t1`
WHERE `i1` >= 101 AND `i1` < 301

2、AHI 有什么好處?

自適應(yīng)哈希索引,英文全稱(chēng) Adaptive Hash Index,簡(jiǎn)稱(chēng) AHI。

根據(jù)上下文需要,后續(xù)內(nèi)容會(huì)混用自適應(yīng)哈希索引和 AHI,不再單獨(dú)說(shuō)明。

顧名思義,自適應(yīng)哈希索引也是一種索引,使用哈希表作為存儲(chǔ)結(jié)構(gòu),自適應(yīng)的意思是我們無(wú)法干涉它的創(chuàng)建、使用、更新、刪除,而是由 InnoDB 自行決定什么時(shí)候創(chuàng)建、怎么創(chuàng)建、什么時(shí)候更新和刪除。

我們知道 InnoDB 的主鍵索引、二級(jí)索引都是 B+ 樹(shù)結(jié)構(gòu)。執(zhí)行 SQL 語(yǔ)句時(shí),以下幾種場(chǎng)景都需要定位到索引葉子結(jié)點(diǎn)中的某條記錄:

  • insert 需要定位到插入目標(biāo)位置的前一條記錄。
  • 查詢(xún)優(yōu)化階段,select、update、delete 預(yù)估掃描區(qū)間記錄數(shù)量時(shí),需要定位到掃描區(qū)間的第一條最后一條記錄。
  • 查詢(xún)執(zhí)行階段,select、update、delete 需要定位到掃描區(qū)間的第一條記錄。

得益于多叉結(jié)構(gòu),B+ 樹(shù)的層級(jí)一般都比較少,通常情況下,從根結(jié)點(diǎn)開(kāi)始,最多經(jīng)過(guò) 3 ~ 4 層就能定位到葉子結(jié)點(diǎn)中的記錄。

這個(gè)定位過(guò)程看起來(lái)挺快的,單次執(zhí)行也確實(shí)比較快,但是對(duì)于頻繁執(zhí)行的場(chǎng)景,還是有一點(diǎn)優(yōu)化空間的。

為了追求極致性能,InnoDB 實(shí)現(xiàn)了 AHI,目的就是能夠根據(jù)搜索條件直接定位到索引葉子結(jié)點(diǎn)中的記錄。

圖片圖片

上圖是一棵高為 4 的 B+ 樹(shù)示意圖,定位索引葉子結(jié)點(diǎn)記錄,需要從根結(jié)點(diǎn)開(kāi)始,經(jīng)過(guò)內(nèi)結(jié)點(diǎn)、葉結(jié)點(diǎn),最后定位到記錄,路徑為:① -> ② -> ③ -> ④

圖片圖片

AHI 會(huì)使用多個(gè) hash 桶來(lái)存儲(chǔ)哈希值到記錄地址的映射,上圖是一個(gè) hash 桶的示意圖。

橙色塊表示記錄地址組成的鏈表,因?yàn)槎鄺l記錄可能被映射到 hash 桶中的同一個(gè)位置。

AHI 定位一條記錄時(shí),根據(jù) where 條件中的某些字段計(jì)算出哈希值,然后直接到 hash 桶中找到對(duì)應(yīng)的記錄。

如果多條記錄被映射到 hash 桶中的同一個(gè)位置,那么找到的是個(gè)鏈表,需要遍歷這個(gè)鏈表以找到目標(biāo)記錄。

3、原理介紹

AHI 能提升定位記錄的效率,但是,有得到必然有付出,天上掉餡餅的事在計(jì)算機(jī)世界里是不會(huì)發(fā)生的。

為了簡(jiǎn)潔,這里我們把定位索引葉子結(jié)點(diǎn)中的記錄簡(jiǎn)稱(chēng)為定位記錄,后面內(nèi)容也依此定義,不再單獨(dú)說(shuō)明。

高效定位記錄,付出的代價(jià)是存儲(chǔ)哈希值到記錄地址的映射占用的內(nèi)存空間、以及構(gòu)造映射花費(fèi)的時(shí)間。

因?yàn)橛袝r(shí)間、空間成本,所以,InnoDB 希望構(gòu)造 AHI 之后,能夠盡可能多的用上,做到收益大于成本。直白點(diǎn)說(shuō),就是需要滿(mǎn)足一定的條件,才能構(gòu)造 AHI。

AHI 采用的是組團(tuán)構(gòu)造邏輯,也就是以數(shù)據(jù)頁(yè)為單位,滿(mǎn)足一定條件之后,就會(huì)為數(shù)據(jù)頁(yè)中的所有記錄構(gòu)造 AHI,主要流程如下:

圖片圖片

第 1 步,為數(shù)據(jù)頁(yè)所屬的索引計(jì)數(shù)(index->search_info->hash_analysis)。

SQL 執(zhí)行過(guò)程中,每次定位某個(gè)索引葉子結(jié)點(diǎn)中的記錄,該索引的計(jì)數(shù)都會(huì)加 1。

如果索引計(jì)數(shù)達(dá)到 17,進(jìn)入第 2 步,否則,執(zhí)行流程到此結(jié)束。

因?yàn)榈?2、3 步為構(gòu)造信息計(jì)數(shù)、為數(shù)據(jù)頁(yè)計(jì)數(shù)也是需要時(shí)間成本的,所以,這里設(shè)置了第 1 道檻,只有索引被使用一定次數(shù)之后,才會(huì)執(zhí)行第 2、3 步。

第 2 步,為構(gòu)造信息計(jì)數(shù)(index->search_info->n_hash_potential)。

對(duì)于 select、insert、update、delete,定位記錄時(shí),搜索條件和葉子結(jié)點(diǎn)中的記錄比較,會(huì)產(chǎn)生兩個(gè)邊界,左邊為下界,右邊為上界,基于下界和上界可以得到用于構(gòu)造 AHI 的信息,我們稱(chēng)之為構(gòu)造信息。

圖片圖片

以上是定位記錄時(shí)產(chǎn)生的下界、上界示意圖。定位記錄過(guò)程中,下界和上界會(huì)不斷向目標(biāo)記錄靠近,最終,下界或上界的其中一個(gè)會(huì)指向目標(biāo)記錄。

如果某次定位記錄時(shí),基于下界或上界得到的構(gòu)造信息,和索引對(duì)象中保存的構(gòu)造信息一致,該構(gòu)造信息計(jì)數(shù)加 1。否則,該索引計(jì)數(shù)清零,構(gòu)造信息計(jì)數(shù)清零或重置為 1(具體見(jiàn)下一小節(jié)的介紹)。

第 3 步,為數(shù)據(jù)頁(yè)計(jì)數(shù)(block->n_hash_helps)。

定位到索引葉子結(jié)點(diǎn)記錄之后,就知道了該記錄所屬的數(shù)據(jù)頁(yè),如果本次得到的構(gòu)造信息和數(shù)據(jù)頁(yè)對(duì)象中保存的構(gòu)造信息相同,數(shù)據(jù)頁(yè)計(jì)數(shù)加 1,否則數(shù)據(jù)頁(yè)計(jì)數(shù)重置為 1。

第 4 步,為數(shù)據(jù)頁(yè)構(gòu)造 AHI。

如果滿(mǎn)足以下兩個(gè)條件,第 3 步的數(shù)據(jù)頁(yè)就具備了構(gòu)造 AHI 的資格:

  • 構(gòu)造信息計(jì)數(shù)大于等于 100。
  • 數(shù)據(jù)頁(yè)計(jì)數(shù)大于數(shù)據(jù)頁(yè)中記錄數(shù)量的十六分之一。

具備構(gòu)造 AHI 的資格之后,對(duì)于以下三種情況之一,會(huì)為數(shù)據(jù)頁(yè)構(gòu)造 AHI:

  • 該數(shù)據(jù)頁(yè)之前沒(méi)有構(gòu)造過(guò) AHI。
  • 該數(shù)據(jù)頁(yè)之前構(gòu)造過(guò) AHI,并且構(gòu)造 AHI 之后,數(shù)據(jù)頁(yè)會(huì)從零開(kāi)始重新計(jì)數(shù),重新計(jì)數(shù)大于數(shù)據(jù)頁(yè)中記錄數(shù)量的兩倍。
  • 該數(shù)據(jù)頁(yè)之前構(gòu)造過(guò) AHI,但是本次確定的構(gòu)造信息和之前不一樣了。

注意:從各個(gè)條件判斷,到最終構(gòu)造 AHI 的整個(gè)流程,并不是在執(zhí)行一條 SQL 的過(guò)程中完成的,而是在執(zhí)行多條 SQL 的過(guò)程中完成的。

到這里,構(gòu)造 AHI 的主要流程就介紹完了,構(gòu)造過(guò)程的具體細(xì)節(jié),請(qǐng)繼續(xù)往下看。

4、構(gòu)造過(guò)程

定位記錄會(huì)調(diào)用 btr_cur_search_to_nth_level(),這也是 AHI 的構(gòu)造入口:

// storage/innobase/btr/btr0cur.cc
void btr_cur_search_to_nth_level(...) {
  ...
  if (btr_search_enabled && 
      !index->disable_ahi) {
    // AHI 的構(gòu)造入口
    btr_search_info_update(cursor);
  }
  ...
}

btr_search_enabled = true(默認(rèn)值),表示 InnoDB 啟用了 AHI。

!index->disable_ahi = true,即 index->disable_ahi = false,表示 index 對(duì)應(yīng)的索引沒(méi)有禁用 AHI。

只有內(nèi)部臨時(shí)表、沒(méi)有主鍵的表禁用了 AHI。

也就是說(shuō),對(duì)于正常的用戶(hù)表、系統(tǒng)表,!index->disable_ahi = true,會(huì)調(diào)用 btr_search_info_update(),進(jìn)入 AHI 的構(gòu)造流程。

(1)索引計(jì)數(shù)

構(gòu)造 AHI 的第 1 步,就是調(diào)用 btr_search_info_update() 進(jìn)行索引計(jì)數(shù)。

// storage/innobase/include/btr0sea.ic
static inline void btr_search_info_update(btr_cur_t *cursor) {
  const auto index = cursor->index;
  ...
  // 索引計(jì)數(shù)加 1
  const auto hash_analysis_value = ++index->search_info->hash_analysis;
  // BTR_SEARCH_HASH_ANALYSIS = 17(硬編碼)
  if (hash_analysis_value < BTR_SEARCH_HASH_ANALYSIS) {
    /* Do nothing */
    return;
  }
  ...
  btr_search_info_update_slow(cursor);
}

btr_search_info_update() 每次被調(diào)用都會(huì)增加索引計(jì)數(shù)(++index->search_info->hash_analysis)。

自增之后,如果索引計(jì)數(shù)小于 17,不需要進(jìn)入 AHI 構(gòu)造流程的下一步,直接返回。

如果索引計(jì)數(shù)大于等于 17,調(diào)用 btr_search_info_update_slow(),進(jìn)入 AHI 構(gòu)造流程的下一步。

看到這里,大家是否會(huì)有疑問(wèn):對(duì)于一條能使用索引的 select 語(yǔ)句,如果 where 條件只有一個(gè)掃描區(qū)間,執(zhí)行過(guò)程中,btr_search_info_update() 最多會(huì)被調(diào)用幾次?

我們通過(guò) 1. 準(zhǔn)備工作小節(jié)的示例 SQL 來(lái)揭曉答案,SQL 如下:

SELECT * FROM `t1`
WHERE `i1` >= 101 AND `i1` < 301

執(zhí)行計(jì)劃如下:

圖片圖片

通過(guò)執(zhí)行計(jì)劃可知,示例 SQL 執(zhí)行過(guò)程中,會(huì)使用索引 idx_i1。

查詢(xún)優(yōu)化階段,MySQL 需要定位到掃描區(qū)間的第一條和最后一條記錄,用于計(jì)算掃描區(qū)間覆蓋的記錄數(shù)量:

  • 定位掃描區(qū)間的第一條記錄,即滿(mǎn)足 id >= 101 的第一條記錄,第 1 次調(diào)用 btr_cur_search_to_nth_level()。
  • 定位掃描掃描區(qū)間的最后一條記錄,即滿(mǎn)足 id < 301 的最后一條記錄,第 2 次調(diào)用 btr_cur_search_to_nth_level()。

查詢(xún)執(zhí)行階段,從存儲(chǔ)引擎讀取第一條記錄之前,需要定位掃描區(qū)間的第一條記錄,即滿(mǎn)足 id >= 101 的第一條記錄,第 3 次調(diào)用 btr_cur_search_to_nth_level()。

定位掃描區(qū)間的第一條和最后一條記錄,都是定位索引葉子結(jié)點(diǎn)中的記錄。

到這里,我們就得到了前面那個(gè)問(wèn)題的答案:3 次。

(2)構(gòu)造信息計(jì)數(shù)

如果某個(gè)索引的計(jì)數(shù)達(dá)到了 17,就會(huì)進(jìn)入 AHI 構(gòu)造流程的第 2 步,根據(jù)本次定位記錄過(guò)程中得到的下界和上界,確定使用索引的前幾個(gè)字段構(gòu)造 AHI,以及對(duì)于索引中前幾個(gè)字段值相同的一組記錄,構(gòu)造 AHI 時(shí)選擇這組記錄的第一條還是最后一條。

3.原理介紹小節(jié)的第 2 步,我們已經(jīng)把這些信息命名為構(gòu)造信息。

基于定位記錄時(shí)得到的下界和上界確定構(gòu)造信息、為構(gòu)造信息計(jì)數(shù)的邏輯由 btr_search_info_update_hash() 完成。

// storage/innobase/btr/btr0sea.cc
void btr_search_info_update_slow(btr_cur_t *cursor) {
  ...
  const auto block = btr_cur_get_block(cursor);
  ...
  btr_search_info_update_hash(cursor);
  ...
}

btr_search_info_update_hash() 的代碼有點(diǎn)長(zhǎng),我們分 2 段介紹。

介紹第 1 段代碼之前,我們先來(lái)看看表示構(gòu)造信息的結(jié)構(gòu)體定義(index->search_info->prefix_info):

// storage/innobase/include/buf0buf.h
// 為了方便閱讀,以下結(jié)構(gòu)體定義對(duì)源碼做了刪減
struct btr_search_prefix_info_t {
  /** recommended prefix: number of bytes in an incomplete field */
  uint32_t n_bytes;
  /** recommended prefix length for hash search: number of full fields */
  uint16_t n_fields;
  /** true or false, depending on whether the leftmost record of several records
  with the same prefix should be indexed in the hash index */
  bool left_side;
}

btr_search_prefix_info_t 包含 3 個(gè)屬性:

  • n_fields、n_bytes:索引的前 n_fields 個(gè)字段,第 n_fields + 1 個(gè)字段的前 n_bytes 字節(jié)用于構(gòu)造 AHI。
  • left_side:如果索引中多條記錄的前 n_fields 個(gè)字段內(nèi)容、第 n_fields + 1 個(gè)字段前 n_bytes 字節(jié)的內(nèi)容相同,我們把這樣的一組記錄稱(chēng)為前綴相同的記錄。對(duì)于前綴相同的記錄:left_side = true 時(shí),選擇最左邊的記錄(第一條記錄)構(gòu)造 AHI。left_side = false 時(shí),選擇最右邊的記錄(最后一條記錄)構(gòu)造 AHI。

接下來(lái),我們開(kāi)始介紹 btr_search_info_update_hash() 的第 1 段代碼邏輯。

// storage/innobase/btr/btr0sea.cc
/****** 第 1 段 ******/
static void btr_search_info_update_hash(btr_cur_t *cursor) {
  dict_index_t *index = cursor->index;
  int cmp;
  ...
  // 索引中,通過(guò)幾個(gè)字段能唯一確定一條記錄?
  // 對(duì)于主鍵索引,n_unique = 主鍵段字?jǐn)?shù)量
  // 對(duì)于二級(jí)索引,n_unique = 二級(jí)索引字段數(shù)量 + 主鍵字段數(shù)量
  const uint16_t n_unique =
      static_cast<uint16_t>(dict_index_get_n_unique_in_tree(index));
  const auto info = index->search_info;
  
  /****** if_1 ******/
  // 構(gòu)造信息計(jì)數(shù)不等于 0
  // 說(shuō)明之前已經(jīng)確定過(guò)構(gòu)造信息了
  if (info->n_hash_potential != 0) {
    // info->prefix_info 中
    // 保存了之前確定的構(gòu)造信息
    const auto prefix_info = info->prefix_info.load();
    ...

    /****** if_2 ******/
    // prefix_info.n_fields
    //   表示之前確定的構(gòu)造信息的字段數(shù)量
    // std::max(up_match, low_match)
    //   表示下界或上界字段數(shù)量中較大的那個(gè)
    if (prefix_info.n_fields == n_unique &&
        std::max(cursor->up_match, cursor->low_match) == n_unique) {
      // 兩個(gè)條件都滿(mǎn)足,說(shuō)明:
      //   如果本次通過(guò)下界、上界確定構(gòu)造信息
      //   會(huì)和之前確定的構(gòu)造信息相同
      //   那么,構(gòu)造信息計(jì)數(shù)加 1
      info->n_hash_potential++;

      return;
    }
    ...
    const bool low_matches_prefix =
        0 >= ut_pair_cmp(prefix_info.n_fields, prefix_info.n_bytes,
                         cursor->low_match, cursor->low_bytes);
    const bool up_matches_prefix =
        0 >= ut_pair_cmp(prefix_info.n_fields, prefix_info.n_bytes,
                         cursor->up_match, cursor->up_bytes);
    /****** if_3 ******/
    // 這里的構(gòu)造信息指的是:
    //   索引對(duì)象中保存的之前確定的構(gòu)造信息
    // prefix_info.left_side = true
    //   如果構(gòu)造信息【大于】下界,且【小于等于】上界
    //   構(gòu)造信息計(jì)數(shù)(n_hash_potential)加 1
    // prefix_info.left_side = false
    //   如果構(gòu)造信息【小于等于】下界,且【大于】上界
    //   構(gòu)造信息計(jì)數(shù)(n_hash_potential)加 1
    if (prefix_info.left_side ? (!low_matches_prefix && up_matches_prefix)
                              : (low_matches_prefix && !up_matches_prefix)) {
      info->n_hash_potential++;
      return;
    }
  }
  ...
}

如果構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)不等于 0,if_1 條件成立,說(shuō)明索引對(duì)象中已經(jīng)保存了之前確定的構(gòu)造信息。

但是,確定索引的 AHI 構(gòu)造信息之后,還需要該索引的構(gòu)造信息計(jì)數(shù)、某個(gè)數(shù)據(jù)頁(yè)的計(jì)數(shù)滿(mǎn)足條件,InnoDB 才會(huì)為該索引的該數(shù)據(jù)頁(yè)構(gòu)造 AHI。

所以,索引對(duì)象中已經(jīng)保存了之前確定的構(gòu)造信息對(duì)應(yīng)兩種情況:

  • 已經(jīng)用索引中保存的構(gòu)造信息為某個(gè)(些)數(shù)據(jù)頁(yè)構(gòu)造了 AHI。
  • 只確定了構(gòu)造信息,還沒(méi)有用它構(gòu)造過(guò) AHI。

構(gòu)造信息計(jì)數(shù)被命名為 n_hash_potential(潛在的),就是因?yàn)榇嬖诘?2 種情況。

if_2、if_3 用于判斷:通過(guò)本次定位記錄時(shí)產(chǎn)生的下界或上界得到構(gòu)造信息,是否和索引對(duì)象中保存的構(gòu)造信息一致,如果一致,則增加構(gòu)造信息計(jì)數(shù)。

if_2 包含兩個(gè)表達(dá)式,如果值都為 true,說(shuō)明上面的判斷結(jié)果為 true,構(gòu)造信息計(jì)數(shù)加 1(info->n_hash_potential++)。

如果 if_2 不成立,再判斷 if_3 是否成立。

前面介紹過(guò),對(duì)于前綴相同的一組記錄,構(gòu)造 AHI 時(shí),由 left_side 決定選擇最左邊還是最右邊的記錄。

對(duì)于本次定位記錄得到的下界、上界,left_side 決定它們?cè)趺春退饕龑?duì)象中保存的構(gòu)造信息比較。

left_side = true 時(shí),如果以下兩個(gè)條件同時(shí)滿(mǎn)足,構(gòu)造信息計(jì)數(shù)(n_hash_potential)加 1:

  • prefix_info.n_fields、n_bytes 大于下界(low_match、low_bytes)。
  • prefix_info.n_fields、n_bytes 小于等于上界(up_match、up_bytes)。

left_side = false 時(shí),如果以下兩個(gè)條件同時(shí)滿(mǎn)足,構(gòu)造信息計(jì)數(shù)(n_hash_potential)加 1:

  • prefix_info.n_fields、n_bytes 小于等于下界(low_match、low_bytes)。
  • prefix_info.n_fields、n_bytes 大于上界(up_match、up_bytes)。

如果 if_3 成立,說(shuō)明本次定位記錄得到的下界或上界的字段數(shù)量、字節(jié)數(shù),和索引對(duì)象中保存的構(gòu)造信息的字段數(shù)量(n_fields)、字節(jié)數(shù)(n_bytes)一致,構(gòu)造信息計(jì)數(shù)加 1(info->n_hash_potential++)。

如果 if_3 不成立,說(shuō)明構(gòu)造信息變了,需要執(zhí)行第 2 段代碼,確定新的構(gòu)造信息,并且重置構(gòu)造信息計(jì)數(shù)。

// storage/innobase/btr/btr0sea.cc
/****** 第 2 段 ******/
static void btr_search_info_update_hash(btr_cur_t *cursor) {
  ...
  info->hash_analysis = 0;

  cmp = ut_pair_cmp(cursor->up_match, cursor->up_bytes, cursor->low_match,
                    cursor->low_bytes);
  /****** if_4 ******/
  if (cmp == 0) {
    // where 條件沒(méi)有定位到匹配的記錄
    // 構(gòu)造信息計(jì)數(shù)清零
    info->n_hash_potential = 0;
    // 雖然給構(gòu)造信息賦值了,但是這個(gè)信息不會(huì)被使用
    /* For extra safety, we set some sensible values here */
    info->prefix_info = {0, 1, true};

  /****** elseif_5 ******/
  } else if (cmp > 0) {
    // 上界(up_match、up_bytes)大于下界(low_match、low_bytes)
    // left_side 都會(huì)設(shè)置為 true
    // 構(gòu)造信息計(jì)數(shù)重置為 1
    info->n_hash_potential = 1;

    ut_ad(cursor->up_match <= n_unique);
    // 如果上界字段數(shù)量等于 n_unique
    // 使用上界作為新的構(gòu)造信息
    if (cursor->up_match == n_unique) {
      info->prefix_info = {
        /* n_bytes   */ 0,
        /* n_fields  */ n_unique,
        /* left_side */ true
      };

    // 下界字段數(shù)量(low_match)小于上界字段數(shù)量(up_match)
    // 使用下界作為新的構(gòu)造信息
    } else if (cursor->low_match < cursor->up_match) {
      info->prefix_info = {
        /* n_bytes   */ 0,
        /* n_fields  */ static_cast<uint16_t>(cursor->low_match + 1),
        /* left_side */ true
      };

    // 下界字段數(shù)量(low_match)等于上界字段數(shù)量(up_match)
    // 下界字節(jié)數(shù)(low_bytes)小于上界字節(jié)數(shù)(up_bytes)
    // 使用下界作為新的構(gòu)造信息
    } else {
      info->prefix_info = {
        /* n_bytes   */ static_cast<uint32_t>(cursor->low_bytes + 1),
        /* n_fields  */ static_cast<uint16_t>(cursor->low_match),
        /* left_side */ true
      };
    }

  /****** else_1 ******/
  } else {
    // 上界(up_match、up_bytes)小于下界(low_match、low_bytes)
    // left_side 都會(huì)設(shè)置為 false
    // 構(gòu)造信息計(jì)數(shù)重置為 1
    info->n_hash_potential = 1;

    ut_ad(cursor->low_match <= n_unique);
    // 如果下界字段數(shù)量等于 n_unique
    // 使用下界作為新的構(gòu)造信息
    if (cursor->low_match == n_unique) {
      info->prefix_info = {
        /* n_bytes   */ 0,
        /* n_fields  */ n_unique,
        /* left_side */ false
      };

    // 下界字段數(shù)量(low_match)大于上界字段數(shù)量(up_match)
    // 使用上界作為新的構(gòu)造信息
    } else if (cursor->low_match > cursor->up_match) {
      info->prefix_info = {
        /* n_bytes   */ 0,
        /* n_fields  */ static_cast<uint16_t>(cursor->up_match + 1),
        /* left_side */ false
      };
    
    // 下界字段數(shù)量(low_match)等于上界字段數(shù)量(up_match)
    // 下界字節(jié)數(shù)(low_bytes)大于上界字節(jié)數(shù)(up_bytes)
    // 使用上界作為新的構(gòu)造信息
    } else {
      info->prefix_info = {
        /* n_bytes   */ static_cast<uint32_t>(cursor->up_bytes + 1),
        /* n_fields  */ static_cast<uint16_t>(cursor->up_match),
        /* left_side */ false
      };
    }
  }
}

第 2 段代碼用于首次或者重新確定構(gòu)造信息,主要邏輯如下:

  • 索引計(jì)數(shù)(info->hash_analysis)清零,下次調(diào)用 btr_search_info_update_hash() 時(shí)重新開(kāi)始計(jì)數(shù)。
  • 如果 left_side = true,并且本次定位記錄得到的下界字段數(shù)量等于 n_unique,使用下界作為新的構(gòu)造信息。
  • 如果 left_side = false,并且本次定位記錄得到的上界字段數(shù)量等于 n_unique,使用上界作為新的構(gòu)造信息。
  • 否則,選擇本次定位記錄得到的上界、下界中較小的那個(gè)作為新的構(gòu)造信息。

ut_pair_cmp(up_match, up_bytes, low_match, low_bytes) 比較本次定位記錄得到的上界、下界,比較結(jié)果保存到 cmp 中。

如果 cmp 等于 0,命中 if_4,說(shuō)明 btr_cur_search_to_nth_level() 定位記錄時(shí),上界、下界相同(up_match 等于 low_match、up_bytes 等于 low_bytes),也就是沒(méi)有找到匹配的記錄,構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)重置為 0。

如果 cmp 大于 0,命中 elseif_5,說(shuō)明本次定位記錄時(shí),下界小于上界,構(gòu)造信息(prefix_info)的 left_side 屬性都會(huì)被設(shè)置為 true。

if (cursor->up_match == n_unique) 條件成立,說(shuō)明搜索條件能夠唯一確定索引中的一條記錄,使用 up_match 作為新構(gòu)造信息的字段數(shù)量,構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)重置為 1,重新開(kāi)始計(jì)數(shù)。

否則,剩下兩種情況,取下界(因?yàn)楸壬辖缧。┳鳛樾聵?gòu)造信息。

cursor->low_match、low_bytes 都從 0 開(kāi)始,變成數(shù)量時(shí)需要加 1。

如果 cmp 小于 0,命中 else_1,說(shuō)明本次定位記錄時(shí),下界大于上界,構(gòu)造信息(prefix_info)的 left_side 屬性都會(huì)被設(shè)置為 false。

if (cursor->low_match == n_unique) 條件成立,說(shuō)明搜索條件能夠唯一確定索引中的一條記錄,使用 low_match 作為新構(gòu)造信息的字段數(shù)量,構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)重置為 1,重新開(kāi)始計(jì)數(shù)。

否則,剩下兩種情況,取上界(因?yàn)楸认陆缧。┳鳛樾聵?gòu)造信息。

cursor->up_match、up_bytes 都從 0 開(kāi)始,變成數(shù)量時(shí)需要加 1。

(3)數(shù)據(jù)頁(yè)計(jì)數(shù)

btr_search_update_block_hash_info() 的主要邏輯分為 2 段:

  • 第 1 段:更新數(shù)據(jù)頁(yè)計(jì)數(shù)。
  • 第 2 段:根據(jù)構(gòu)造信息計(jì)數(shù)、數(shù)據(jù)頁(yè)計(jì)數(shù),決定是否需要為 block 對(duì)應(yīng)的數(shù)據(jù)頁(yè)構(gòu)造或重新構(gòu)造 AHI。

先來(lái)看第 1 段代碼:

// storage/innobase/btr/btr0sea.cc
/****** 第 1 段 ******/
static bool btr_search_update_block_hash_info(...) {
  ...
  const auto info = cursor->index->search_info;
  // last_hash_succ 用于判斷 where 條件是否命中了 AHI
  // 先設(shè)置為 false
  info->last_hash_succ = false;
  ...
  // 數(shù)據(jù)頁(yè)計(jì)數(shù)是否大于 0
  if (block->n_hash_helps > 0 && 
      // 構(gòu)造信息計(jì)數(shù)是否大于 0
      info->n_hash_potential > 0 &&
      // 數(shù)據(jù)頁(yè)對(duì)象中保存的構(gòu)造信息
      //  (可能還沒(méi)有用來(lái)構(gòu)造過(guò) AHI)
      // 和本次確定的構(gòu)造信息是否相同
      block->ahi.recommended_prefix_info.load() == info->prefix_info.load()) {
    if (block->ahi.index &&
        block->ahi.prefix_info.load() == info->prefix_info.load()) {
      // 數(shù)據(jù)頁(yè)對(duì)象中保存的構(gòu)造信息(用來(lái)構(gòu)造過(guò) AHI)
      // 和本次確定的構(gòu)造信息相同
      // 說(shuō)明 where 條件命中了 AHI
      // 把 info->last_hash_succ 設(shè)置為 true
      // 下一篇文章講 AHI 命中時(shí)會(huì)用到這個(gè)屬性
      info->last_hash_succ = true;
    }
    // 構(gòu)造信息沒(méi)有變化,構(gòu)造信息計(jì)數(shù)加 1
    block->n_hash_helps++;
  } else {
    // 構(gòu)造信息變了,重置數(shù)據(jù)頁(yè)計(jì)數(shù)
    block->n_hash_helps = 1;
    // 新確定的構(gòu)造信息保存到數(shù)據(jù)頁(yè)對(duì)象中
    block->ahi.recommended_prefix_info = info->prefix_info.load();
  }
  ...
}

如果以下 3 個(gè)條件都滿(mǎn)足:

  • 數(shù)據(jù)頁(yè)計(jì)數(shù)(block->n_hash_helps)大于 0。
  • 構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)大于 0。
  • 數(shù)據(jù)頁(yè)對(duì)象中保存的構(gòu)造信息(block->ahi.recommended_prefix_info)和本次確定的構(gòu)造信息相同。

說(shuō)明數(shù)據(jù)頁(yè)的構(gòu)造信息沒(méi)有變化,數(shù)據(jù)頁(yè)計(jì)數(shù)加 1(block->n_hash_helps++)。

否則,數(shù)據(jù)頁(yè)計(jì)數(shù)重置為 1,并且保存新的構(gòu)造信息到數(shù)據(jù)頁(yè)對(duì)象中(block->ahi.recommended_prefix_info)。

// storage/innobase/btr/btr0sea.cc
/****** 第 2 段 ******/
static bool btr_search_update_block_hash_info(...) {
  ...
  // BTR_SEARCH_BUILD_LIMIT = 100
  // 同一個(gè)索引的 AHI 構(gòu)造信息
  // 連續(xù) 100 次相同
  if (info->n_hash_potential >= BTR_SEARCH_BUILD_LIMIT &&
      // BTR_SEARCH_PAGE_BUILD_LIMIT = 16
      // 同樣的 AHI 構(gòu)造信息
      // 命中同一個(gè)數(shù)據(jù)頁(yè)的次數(shù)
      // 達(dá)到了該數(shù)據(jù)頁(yè)中記錄數(shù)量的十六分之一
      block->n_hash_helps >
          page_get_n_recs(block->frame) / BTR_SEARCH_PAGE_BUILD_LIMIT) {
    // 滿(mǎn)足上面 2 個(gè)條件之后
    // 如果之前沒(méi)有構(gòu)造過(guò) AHI,則返回 true
    // 表示本次需要構(gòu)造 AHI
    if (!block->ahi.index ||
        // 如果之前已經(jīng)構(gòu)造過(guò) AHI
        // 需要命中同一個(gè)數(shù)據(jù)頁(yè)的次數(shù)
        //   達(dá)到該數(shù)據(jù)頁(yè)中記錄數(shù)量的 2 倍
        // 或者 AHI 構(gòu)造信息發(fā)生了變化
        // 才會(huì)重新構(gòu)造 AHI
        block->n_hash_helps > 2 * page_get_n_recs(block->frame) ||
        block->ahi.recommended_prefix_info.load() !=
            block->ahi.prefix_info.load()) {
      return true;
    }
  }

  // 不滿(mǎn)足上面的一系列條件
  // 返回 false
  // 表示本次不需要構(gòu)造 AHI
  return false;
}

第 2 段代碼,用于判斷是否需要為數(shù)據(jù)頁(yè)(block)構(gòu)造或重新構(gòu)造 AHI,滿(mǎn)足以下兩個(gè)條件,說(shuō)明具備了為該數(shù)據(jù)頁(yè)構(gòu)造 AHI 的資格:

  • 構(gòu)造信息計(jì)數(shù)(info->n_hash_potential)大于等于 100。
  • 數(shù)據(jù)頁(yè)計(jì)數(shù)(block->n_hash_helps)大于數(shù)據(jù)頁(yè)中記錄數(shù)量的十六分之一。

數(shù)據(jù)頁(yè)(block)具備構(gòu)造 AHI 的資格之后,只有以下三種情況會(huì)構(gòu)造或重新構(gòu)造 AHI:

  • 該數(shù)據(jù)頁(yè)之前沒(méi)有構(gòu)造過(guò) AHI(!block->ahi.index 為 true)。
  • 該數(shù)據(jù)頁(yè)之前構(gòu)造過(guò) AHI,構(gòu)造信息沒(méi)有發(fā)生變化,但是數(shù)據(jù)頁(yè)計(jì)數(shù)大于數(shù)據(jù)頁(yè)中記錄數(shù)量的兩倍(2 * page_get_n_recs(block->frame))。
  • 該數(shù)據(jù)頁(yè)之前構(gòu)造過(guò) AHI,但是構(gòu)造信息變了。

(4)構(gòu)造 AHI

滿(mǎn)足前面一系列條件之后,就可以為數(shù)據(jù)頁(yè)構(gòu)造 AHI 了。

// storage/innobase/btr/btr0sea.cc
static void btr_search_build_page_hash_index(...) {
  ...
  // block 是本次需要構(gòu)造 AHI 的數(shù)據(jù)頁(yè)控制塊
  // page 是數(shù)據(jù)頁(yè)對(duì)象
  const auto page = buf_block_get_frame(block);
  // 數(shù)據(jù)頁(yè)的 AHI 構(gòu)造信息
  const auto prefix_info = block->ahi.recommended_prefix_info.load();
  const auto n_fields_for_offsets = btr_search_get_n_fields(prefix_info);

  // 刪除之前為該數(shù)據(jù)頁(yè)構(gòu)造的 AHI 記錄
  if (block->ahi.index && block->ahi.prefix_info.load() != prefix_info) {
    btr_search_drop_page_hash_index(block);
  }
  ...
  // 數(shù)據(jù)頁(yè)中的記錄數(shù)量
  const auto n_recs = page_get_n_recs(page);
  ...
  // page_get_infimum_rec(page) 讀取 infimum 偽記錄
  // page_rec_get_next() 讀取數(shù)據(jù)頁(yè)中的第 1 條用戶(hù)記錄
  auto rec = page_rec_get_next(page_get_infimum_rec(page));

  Rec_offsets offsets;
  ...
  // 用于構(gòu)造第 1 條用戶(hù)記錄的 AHI 的 hash 種子
  const auto index_hash = btr_hash_seed_for_record(index);
  // 計(jì)算第 1 條用戶(hù)記錄的 AHI hash 值
  auto hash_value =
      rec_hash(rec, offsets.compute(rec, index, n_fields_for_offsets),
               prefix_info.n_fields, prefix_info.n_bytes, index_hash, index);

  size_t n_cached = 0;
  // left_side = true
  // 對(duì)于前綴相同的一組記錄(可能有一條或多條記錄)
  // 標(biāo)記需要為這一組的第一條記錄構(gòu)造 AHI
  if (prefix_info.left_side) {
    hashes[n_cached] = hash_value;
    recs[n_cached] = rec;
    n_cached++;
  }

  // 循環(huán),標(biāo)記需要為數(shù)據(jù)頁(yè)中第 2 條及以后的用戶(hù)記錄構(gòu)造 AHI
  for (;;) {
    const auto next_rec = page_rec_get_next(rec);
    if (page_rec_is_supremum(next_rec)) {
      // left_side = false 時(shí)
      // 標(biāo)記需要為 supremum 偽記錄構(gòu)造 AHI
      // 然后結(jié)束循環(huán)
      if (!prefix_info.left_side) {
        hashes[n_cached] = hash_value;
        recs[n_cached] = rec;
        n_cached++;
      }

      break;
    }
    // 為當(dāng)前循環(huán)的記錄計(jì)算用于構(gòu)造 AHI 的 hash 值
    const auto next_hash_value = rec_hash(
        next_rec, offsets.compute(next_rec, index, n_fields_for_offsets),
        prefix_info.n_fields, prefix_info.n_bytes, index_hash, index);
    // hash_value != next_hash_value
    // 說(shuō)明換了一組記錄
    if (hash_value != next_hash_value) {
      /* Insert an entry into the hash index */
      // left_side = true
      // 為下一組的第一條記錄構(gòu)造 AHI
      if (prefix_info.left_side) {
        hashes[n_cached] = next_hash_value;
        recs[n_cached] = next_rec;
        n_cached++;
      } else {
        // left_side = false
        // 為本組的最后一條記錄構(gòu)造 AHI
        hashes[n_cached] = hash_value;
        recs[n_cached] = rec;
        n_cached++;
      }
    }

    rec = next_rec;
    hash_value = next_hash_value;
  }
  ...
  // 為數(shù)據(jù)頁(yè)構(gòu)造 AHI 之后
  // 重置數(shù)據(jù)頁(yè)計(jì)數(shù)
  block->n_hash_helps = 0;
  // 已經(jīng)用來(lái)構(gòu)造過(guò) AHI 構(gòu)造信息保存到
  // 數(shù)據(jù)頁(yè)對(duì)象的 ahi.prefix_info 屬性中
  block->ahi.prefix_info = prefix_info;
  block->ahi.index = index;
  // 把每條記錄的 AHI hash 值和記錄地址
  // 插入到 AHI hash 表中
  const auto table = btr_get_search_table(index);
  for (size_t i = 0; i < n_cached; i++) {
    ha_insert_for_hash(table, hashes[i], block, recs[i]);
  }
  ...
}

為數(shù)據(jù)頁(yè)構(gòu)造 AHI 主要分為三大步驟:

  • 循環(huán)讀取數(shù)據(jù)頁(yè)中的記錄,每讀取一條記錄,根據(jù) AHI 構(gòu)造信息計(jì)算記錄的哈希值,把哈希值保存到 hashes 數(shù)組中、記錄地址保存到 recs 數(shù)組中,一條記錄在 hashs、recs 數(shù)組中的下標(biāo)一樣,都是 n_cached。這一步有個(gè)特殊邏輯需要處理,就是對(duì)于前綴相同的一組記錄,根據(jù) left_side 決定為第一條還是最后一條記錄構(gòu)造 AHI。
  • 數(shù)據(jù)頁(yè)計(jì)數(shù)(block->n_hash_helps)重置為 0,重新開(kāi)始計(jì)數(shù),用于為該數(shù)據(jù)頁(yè)重新構(gòu)造 AHI 作準(zhǔn)備。然后,把構(gòu)造信息(prefix_info)、數(shù)據(jù)頁(yè)所屬的索引(index)分別保存到數(shù)據(jù)頁(yè)對(duì)象的 ahi.prefix_info、ahi.index 屬性中,btr_search_update_block_hash_info() 會(huì)用到這兩個(gè)屬性。
  • 把 hashs、recs 數(shù)組中的哈希值、記錄地址一一對(duì)應(yīng)的插入到哈希表中,每條記錄的哈希值映射到該記錄的地址。

4、總結(jié)

AHI 構(gòu)造流程的前三步都是在判斷是否滿(mǎn)足某些條件,這些條件的范圍從大到小。

先是索引級(jí)別,判斷索引被命中的次數(shù)。

然后,是索引級(jí)別的構(gòu)造信息計(jì)數(shù)。

構(gòu)造信息來(lái)源于定位記錄過(guò)程中產(chǎn)生的下界、上界,其源頭是 where 條件,我們可以把它看成對(duì) where 條件的抽象,或者更具體點(diǎn),把它看成 where 條件的分類(lèi)。

某個(gè)構(gòu)造信息的計(jì)數(shù)達(dá)到指定次數(shù),意味著如果根據(jù)這個(gè)構(gòu)造信息(或者說(shuō)這類(lèi) where 條件)構(gòu)造 AHI,命中率會(huì)比較高。

InnoDB 以數(shù)據(jù)頁(yè)為單位,一次性為某個(gè)數(shù)據(jù)頁(yè)中的所有記錄構(gòu)造 AHI。

構(gòu)造信息計(jì)數(shù)滿(mǎn)足條件之后,還需要進(jìn)一步?jīng)Q定為哪些數(shù)據(jù)頁(yè)構(gòu)造 AHI,于是就有了數(shù)據(jù)頁(yè)計(jì)數(shù)(實(shí)際上是數(shù)據(jù)頁(yè)級(jí)別的構(gòu)造信息計(jì)數(shù))。

當(dāng)索引計(jì)數(shù)、構(gòu)造信息計(jì)數(shù)、數(shù)據(jù)頁(yè)計(jì)數(shù)都滿(mǎn)足條件之后,某個(gè)數(shù)據(jù)頁(yè)就初步具備了構(gòu)造 AHI 的資格,最后,還會(huì)根據(jù)該數(shù)據(jù)頁(yè)是否構(gòu)造過(guò) AHI、構(gòu)造信息是否發(fā)生變化等條件,做出終極決定:是否為該數(shù)據(jù)頁(yè)構(gòu)造 AHI。

本文轉(zhuǎn)載自微信公眾號(hào)「一樹(shù)一溪」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系一樹(shù)一溪公眾號(hào)。

責(zé)任編輯:姜華 來(lái)源: 一樹(shù)一溪
相關(guān)推薦

2025-04-08 08:20:00

2023-04-12 16:45:07

MySQL索引數(shù)據(jù)結(jié)構(gòu)

2017-06-06 10:30:12

前端Web寬度自適應(yīng)

2012-05-09 10:58:25

JavaMEJava

2014-09-05 10:10:32

Android自適應(yīng)布局設(shè)計(jì)

2010-08-30 09:52:03

DIV高度自適應(yīng)

2010-08-30 10:26:20

DIV自適應(yīng)高度

2022-04-26 10:13:00

哈希索引MySQLInnoDB

2023-10-23 08:48:04

CSS寬度標(biāo)題

2024-05-22 09:31:07

2025-01-21 08:00:00

自適應(yīng)框架框架開(kāi)發(fā)

2022-04-12 07:48:57

云技術(shù)SDN網(wǎng)絡(luò)

2022-10-24 17:57:06

CSS容器查詢(xún)

2011-05-12 11:28:20

按比例縮放

2009-04-23 11:24:09

2021-11-01 23:57:03

數(shù)據(jù)庫(kù)哈希索引

2017-08-16 14:08:46

Android O圖標(biāo)視覺(jué)

2010-08-26 16:27:46

CSS高度

2017-04-13 11:20:37

圖片寬度解決方案前端

2014-04-15 13:09:08

Android配色colour
點(diǎn)贊
收藏

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

久久夜色精品国产欧美乱| 欧美日在线观看| 91在线观看免费高清完整版在线观看| 免费中文字幕日韩| 超碰97久久| 狠狠色噜噜狠狠狠狠97| 视频一区亚洲| 国产 欧美 自拍| 日韩avvvv在线播放| 精品国产一区二区三区四区在线观看 | 中文字幕在线播放| 国产福利精品一区二区| 日本欧美中文字幕| 亚洲av无码一区二区三区在线| 国产成人一二片| 一本一本久久a久久精品综合麻豆| 欧美一区2区三区4区公司二百| av中文字幕观看| 丝袜诱惑制服诱惑色一区在线观看| 色老头一区二区三区在线观看| 亚洲视频 中文字幕| 国产精品99久久久久久董美香| 亚洲综合色视频| 一区二区三区在线观看www| 国产特级黄色片| 日本视频一区二区| 91精品国产乱码久久久久久久久| 日本二区三区视频| 九九综合久久| 亚洲国产精品久久久久秋霞不卡| 一区二区三区视频在线观看免费| av在线视屏| 国产精品丝袜久久久久久app| 久久国产精品一区二区三区| 精品国产九九九| 麻豆精品一区二区三区| 91av在线国产| 日韩xxxxxxxxx| 欧美日韩一区二区高清| 久久激情五月丁香伊人| 五月婷六月丁香| 国产亚洲一卡2卡3卡4卡新区| 亚洲精品xxx| 国产伦精品一区二区三区精品| 国产一区精品二区| 91精品国产综合久久久久久漫画| 丰满少妇在线观看| 亚洲成人短视频| 色欧美片视频在线观看在线视频| 天堂…中文在线最新版在线| heyzo高清国产精品| 亚洲一区二区偷拍精品| 毛片av在线播放| 欧美日韩在线视频免费观看| 亚洲欧美日韩国产中文在线| 亚洲区成人777777精品| 黄在线免费看| 一区二区三区四区在线播放 | 欧美草逼视频| 亚洲一区在线观看免费观看电影高清 | 国产精品免费一区豆花| 日本成人一级片| 免费成人av在线播放| 国产精品久久久久一区二区| 中文字幕a级片| 久久99久久99| 91视频网页| 午夜在线视频观看| 久久婷婷综合激情| 日韩欧美亚洲v片| 网友自拍视频在线| 亚洲精品久久久久久国产精华液| 中文字幕在线中文| caoporn视频在线观看| 欧美日韩免费看| 黄色在线视频网| 57pao成人永久免费| 日韩欧美国产一二三区| 波多野结衣加勒比| 国内精品视频在线观看| 一个色综合导航| 欧美爱爱免费视频| 9国产精品视频| 国产精品国语对白| av 一区二区三区| 91性感美女视频| 一区不卡视频| 污视频免费在线观看| 五月天网站亚洲| 亚洲成人av免费看| 日韩精品视频在线看| 日韩精品欧美国产精品忘忧草| 蜜桃久久精品成人无码av| 午夜影院欧美| 9.1国产丝袜在线观看| 中国一区二区视频| 成人午夜精品在线| 亚洲三级一区| 国产精品电影| 3d成人动漫网站| 亚洲永久无码7777kkk| 欧美激情另类| 26uuu亚洲国产精品| 国产精品久久久久久久一区二区| 成人av资源网站| 一区二区不卡在线观看| a天堂资源在线| 91精品国产色综合久久不卡蜜臀| 熟妇高潮精品一区二区三区| 国产精品毛片一区二区在线看| 欧美激情亚洲综合一区| 伊人精品一区二区三区| 99久久国产免费看| 最近免费观看高清韩国日本大全| 欧美动物xxx| 亚洲国产精品yw在线观看 | 日韩欧美午夜| 欧美中在线观看| 精品人妻无码一区二区三区蜜桃一 | 人妻无码中文久久久久专区| 一个色综合网| 国产精品永久免费在线| 色视频在线观看| 一区二区三区不卡视频| 97超碰成人在线| 国产91一区| 5566成人精品视频免费| 亚洲精品无遮挡| 亚洲欧美激情插 | 在线成人激情视频| 日韩三级一区二区三区| 国产1区2区3区精品美女| 香蕉视频在线网址| 男人天堂久久| 神马久久桃色视频| 久久精品五月天| 久久综合久色欧美综合狠狠| 欧美一级视频免费看| 视频一区日韩| 久久91精品国产91久久久| 国产模特av私拍大尺度| 亚洲天堂网中文字| 亚洲美女性囗交| 国产精品成人a在线观看| 国产精品久久久久久久久久99| 日本亚洲一区| 一本大道综合伊人精品热热| 动漫精品一区二区三区| 噜噜噜久久亚洲精品国产品小说| 蜜桃视频在线观看成人| 欧美办公室脚交xxxx| 亚洲国产欧美一区二区丝袜黑人| 国产真实夫妇交换视频| 成人免费黄色在线| 国产婷婷一区二区三区| 久久综合五月婷婷| 欧美中文字幕在线| h视频在线观看免费| 欧美三级电影一区| 久久精品一区二区三区四区五区| 国产呦精品一区二区三区网站| 干日本少妇视频| 中文字幕日韩高清在线| 97久久国产精品| 日韩av成人| 欧美三级视频在线观看| 日韩一区二区三区四区在线| 韩国视频一区二区| 国产精品免费看久久久无码| 久久99国产精品久久99大师| 91精品国产高清| 国产视频精品久久| 欧美久久久一区| 激情四射综合网| 26uuu精品一区二区在线观看| av天堂永久资源网| 欧美一区电影| 成人亲热视频网站| 国产丝袜在线播放| 亚洲精品视频网上网址在线观看| 人人妻人人爽人人澡人人精品 | 国产伦精品一区二区三区视频青涩 | 视频一区视频二区中文字幕| 亚洲电影一二三区| 欧洲大片精品免费永久看nba| 97国产在线观看| 岛国最新视频免费在线观看| 日韩限制级电影在线观看| 日操夜操天天操| 日本一区二区三区国色天香 | 欧美www在线| 视频在线不卡| 欧美一区二区三区啪啪| 国产精品久久久免费视频| 国产欧美精品一区| 中国男女全黄大片| 视频一区国产视频| 国产高清不卡无码视频| 精品国产乱码久久久久久果冻传媒| 91精品综合视频| 咪咪网在线视频| 久久亚洲精品中文字幕冲田杏梨| 天堂av资源在线| 91精品国产综合久久精品性色| 九九热在线视频播放| 国产精品福利一区二区| 国产精品久久久久久久无码| 久久国内精品自在自线400部| 亚洲国产精品无码观看久久| 久久电影院7| 精品无码久久久久国产| 日韩av黄色| 国产91精品久久久| 青青在线视频| 久久精品电影一区二区| 国产一区二区三区不卡在线| 337p日本欧洲亚洲大胆精品| 亚洲中文字幕在线观看| 欧美日韩亚洲天堂| 精国产品一区二区三区a片| 国产精品你懂的在线欣赏| 久久精品综合视频| 国产精品一区二区在线看| 三年中国国语在线播放免费| 亚洲激情自拍| 乱熟女高潮一区二区在线| 99精品全国免费观看视频软件| 欧美精品在线一区| 精品三级av在线导航| 亚洲www在线观看| 另类一区二区| 国产精品视频资源| 三级成人在线| 日韩av手机在线观看| av福利在线导航| 久久久久成人网| 日韩激情美女| 欧美二区在线播放| 成人黄色在线电影| 久久久国产成人精品| av大片在线观看| 在线观看欧美成人| 国产天堂素人系列在线视频| 亚洲精品日韩丝袜精品| 天堂av电影在线观看| 日韩av在线免费| 欧美性孕妇孕交| 亚洲欧美日韩国产成人| 色播色播色播色播色播在线| 精品无人区乱码1区2区3区在线| 手机看片一区二区| 日韩av影视综合网| 亚洲欧美一区二区三| 日韩理论片久久| 三级国产在线观看| 亚洲情综合五月天| 成黄免费在线| 色先锋资源久久综合5566| 淫片在线观看| 久久久999精品免费| 在线中文免费视频| 久久久久久国产精品三级玉女聊斋| 动漫一区二区| 91av视频在线| 日韩av一级| 92看片淫黄大片欧美看国产片| 国产亚洲精aa在线看| av日韩免费电影| 红杏aⅴ成人免费视频| 狼狼综合久久久久综合网| 欧美极品在线观看| 一区二区三区四区国产| 综合激情视频| 成人免费aaa| 美女视频一区二区三区| 制服.丝袜.亚洲.中文.综合懂| 成人午夜碰碰视频| 国产三级av在线播放| 亚洲色大成网站www久久九九| 久久无码精品丰满人妻| 精品女同一区二区三区在线播放| 国产午夜无码视频在线观看| 欧美疯狂做受xxxx富婆| 性欧美8khd高清极品| 亚洲人成电影在线观看天堂色| 一级毛片视频在线观看| 欧美激情在线视频二区| 国产精品一区二区av影院萌芽| 成人欧美一区二区三区在线| 国产精品极品| 亚洲黄色一区二区三区| 在线观看免费一区二区| 九色在线视频观看| 寂寞少妇一区二区三区| 国产xxxx视频| 国产精品免费av| 日韩av在线天堂| 777亚洲妇女| 色视频在线观看| 欧美老少配视频| 影视一区二区三区| 99久久精品免费看国产四区| av亚洲免费| 日本十八禁视频无遮挡| 久久精品99国产精品| 三上悠亚ssⅰn939无码播放 | 成人午夜视频在线播放| 欧美一区二区三区思思人| 日本高清中文字幕二区在线| 欧美成人精品三级在线观看| 三上悠亚激情av一区二区三区| 97视频中文字幕| 日韩欧美电影| 欧美精品色婷婷五月综合| 国产成人一区二区精品非洲| 日本成人免费在线观看| 好吊成人免视频| 国产叼嘿视频在线观看| 色婷婷av一区二区三区久久| 在线女人免费视频| 国产精品对白一区二区三区| 久久久久久美女精品| 男人搞女人网站| 91老司机福利 在线| 久久综合亚洲色hezyo国产| 欧美日韩国产综合视频在线观看 | 又粗又黑又大的吊av| 国产成人av一区| 国产日韩欧美在线观看视频| 欧美午夜精品一区二区蜜桃| 免费理论片在线观看播放老| 久久频这里精品99香蕉| 亚洲成人影音| 性生活免费观看视频| 精品一区二区三区免费毛片爱| 91成人精品一区二区| 欧美综合久久久| 国产三级视频在线| 日本视频久久久| 亚洲天堂日韩在线| 亚洲 欧美 日韩 国产综合 在线 | 制服丝袜中文字幕在线| 成人福利在线视频| 9999国产精品| 亚洲欧美手机在线| 亚洲色图一区二区三区| 91成人在线免费| 日韩在线观看你懂的| 日韩毛片免费视频一级特黄| 影音欧美亚洲| 激情六月婷婷综合| 紧身裙女教师波多野结衣| 日韩欧美一级特黄在线播放| 99福利在线| 99国精产品一二二线| 在线欧美福利| 中文字幕影片免费在线观看| 午夜精品aaa| 视频二区在线| 国产精品美女久久| 一区二区三区四区日韩| 日本r级电影在线观看| 亚洲综合区在线| 无码精品人妻一区二区三区影院| 欧美在线视频播放| 欧美丝袜激情| 亚洲免费黄色录像| 夜夜嗨av一区二区三区中文字幕| 亚洲精品97久久中文字幕无码| 久久久在线视频| 亚洲理论电影| 自拍偷拍一区二区三区四区| **网站欧美大片在线观看| 国产99对白在线播放| 欧美激情亚洲激情| 黑人操亚洲人| 制服丝袜中文字幕第一页 | 日韩中文字幕亚洲| 免费观看亚洲视频大全| 亚洲人成无码网站久久99热国产 | 神马影院一区二区三区| 国产一区二区调教| 国产污片在线观看| 国产一区二区三区精品久久久| 伊人久久一区| 欧美极品欧美精品欧美图片| 亚洲丝袜制服诱惑| 天天色棕合合合合合合合| 国产精品成人在线| 麻豆亚洲av熟女国产一区二| 日本丰满少妇一区二区三区| 在线观看av的网站| 欧美不卡一区二区三区| 色偷偷色偷偷色偷偷在线视频| 日韩黄色影视| 懂色中文一区二区在线播放| 一级片免费在线播放| 欧美久久精品午夜青青大伊人| 婷婷亚洲精品| 青娱乐精品在线|