国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

GreatSQL Hash Join 條件列長度對執(zhí)行計劃的影響

數(shù)據(jù)庫 其他數(shù)據(jù)庫
調(diào)查生成 Join 的執(zhí)行計劃代碼,發(fā)現(xiàn)在優(yōu)化器執(zhí)行JOIN::create_root_access_path_for_join的時候,有一個連接條件內(nèi)部長度的判斷過程,這里用了一個固定長度1024作為內(nèi)部長度的判斷,當(dāng)超過這個長度的時候就要另外執(zhí)行一次過濾器。

一、問題發(fā)現(xiàn)

在一次開發(fā)中發(fā)現(xiàn)當(dāng)執(zhí)行 Hash Join 用 VARCHAR 字段作為連接的時候,字段長度長短不同時候,執(zhí)行計劃也不一樣。看下面3個例子。

1、連接條件字段長度為20的場景

greatsql> CREATE TABLE t1 (c1 INT, c2 varchar(20)) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t1 VALUES (1,'aa'),(2,'bb'),(35,'cc'),(5,'dd'),(null,'eeff');
greatsql> CREATE TABLE t3 (ccc1 INT, ccc2 varchar(20)) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t3 VALUES (1,'aa11bb'),(2,'dd1cc'),(3,'ee1dd'),(4,'dd2'),(null,'eeff');

兩張表執(zhí)行 Hash Join 連接,用 VARCHAR 作為連接條件的結(jié)果:

greatsql> EXPLAIN format=tree SELECT * FROM t1 JOIN t3 ON t1.c2=t3.ccc2;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                           |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Innerhashjoin (t3.ccc2 = t1.c2)  (cost=3.50rows=5)
    -> Tablescanon t3  (cost=0.07rows=5)
    -> Hash
        -> Tablescanon t1  (cost=0.75rows=5)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+

2、連接條件字段長度為1000的場景

greatsql> CREATE TABLE t1 (c1 INT, c2 varchar(1000)) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t1 VALUES (1,'aa'),(2,'bb'),(35,'cc'),(5,'dd'),(null,'eeff');
greatsql> CREATE TABLE t3 (ccc1 INT, ccc2 varchar(1000)) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t3 VALUES (1,'aa11bb'),(2,'dd1cc'),(3,'ee1dd'),(4,'dd2'),(null,'eeff');

兩張表執(zhí)行 Hash Join 連接,用 VARCHAR 作為連接條件的結(jié)果:

greatsql> EXPLAIN format=tree SELECT * FROM t1 JOIN t3 ON t1.c2=t3.ccc2;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                          |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: (t3.ccc2 = t1.c2)  (cost=3.52rows=5) 這里另外需要一次過濾比較
    -> Innerhashjoin (<hash>(t3.ccc2)=<hash>(t1.c2))  (cost=3.52rows=5)
        -> Tablescanon t3  (cost=0.07rows=5)
        -> Hash
            -> Tablescanon t1  (cost=0.75rows=5)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

3、連接條件字段類型為 BLOB 的場景

greatsql> CREATE TABLE t11 (c1 INT, c2 blob) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t11 VALUES (1,'aa'),(2,'bb'),(35,'cc'),(5,'dd'),(null,'eeff');
greatsql> CREATE TABLE t13 (ccc1 INT, ccc2 blob) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
greatsql> INSERT INTO t13 VALUES (1,'aa11bb'),(2,'dd1cc'),(3,'ee1dd'),(4,'dd2'),(null,'eeff');

兩張表執(zhí)行 Hash Join 連接,用 BLOB 作為連接條件的結(jié)果:

greatsql> EXPLAIN format=tree SELECT * FROM t11 JOIN t3 ON t11.c2=t13.ccc2;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: (t13.ccc2 = t11.c2)  (cost=3.52rows=5) 這里另外需要一次過濾比較
    -> Innerhashjoin (<hash>(t13.ccc2)=<hash>(t11.c2))  (cost=3.52rows=5)
        -> Tablescanon t13  (cost=0.07rows=5)
        -> Hash
            -> Tablescanon t11  (cost=0.75rows=5)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

通過以上三個例子發(fā)現(xiàn),當(dāng)連接字段超過一定長度的時候,執(zhí)行計劃會在 Hash Join 外面再套一層 FilterIterator 另外進(jìn)行一次過濾比較。

二、問題調(diào)查過程

調(diào)查生成 Join 的執(zhí)行計劃代碼,發(fā)現(xiàn)在優(yōu)化器執(zhí)行JOIN::create_root_access_path_for_join的時候,有一個連接條件內(nèi)部長度的判斷過程,這里用了一個固定長度1024作為內(nèi)部長度的判斷,當(dāng)超過這個長度的時候就要另外執(zhí)行一次過濾器。

// 調(diào)查代碼發(fā)現(xiàn)跟下面代碼的長度判斷有關(guān),如果計算出來的內(nèi)部長度超過1024最后還是要執(zhí)行FilterIterator
HashJoinCondition::HashJoinCondition(Item_eq_base *join_condition,
                                     MEM_ROOT *mem_root) {
  m_store_full_sort_key = true;

constbool using_secondary_storage_engine =
      (current_thd->lex->m_sql_cmd != nullptr &&
       current_thd->lex->m_sql_cmd->using_secondary_storage_engine());
if ((join_condition->compare_type() == STRING_RESULT ||
       join_condition->compare_type() == ROW_RESULT) &&
      !using_secondary_storage_engine) {
    const CHARSET_INFO *cs = join_condition->compare_collation();
    // 這里的1024是開發(fā)者隨意寫的,但是決定了最后要不要在join外面再執(zhí)行一次過濾,計算公式見下面三的表格
    if (cs->coll->strnxfrmlen(cs, cs->mbmaxlen * m_max_character_length) > 1024) { 
      m_store_full_sort_key = false;
    }
  }
}

static AccessPath *CreateHashJoinAccessPath() {
// 下面這段跟連接條件的長度計算有關(guān),如果超過1024長度會向hash_join_extra_conditions隊列插入condition,從而最后走過濾器
for (const HashJoinCondition &cond : hash_join_conditions) {
    if (!cond.store_full_sort_key()) {
      hash_join_extra_conditions.push_back(cond.join_condition());
    }
  }
}

對比前兩個場景的執(zhí)行計劃:

字段長度

AccessPath

參數(shù)

說明

20

HashJoinIterator

m_build_input=TableScanIterator (t3表) m_probe_input=TableScanIterator (t1表) />m_row_buffer.m_join_conditions=Item_func_eq

HashJoinIterator內(nèi)部創(chuàng)建一張hash表,hash表掃描第二張表的時候通過m_row_buffer.m_join_conditions進(jìn)行數(shù)據(jù)過濾

1000

FilterIterator

m_source=HashJoinIterator m_condition=Item_func_like

HashJoinIterator外面套一層filter,用m_condition再執(zhí)行一次過濾

之所以做這個限制,具體原因可以看一下m_store_full_sort_key這個參數(shù)的注釋,這個解釋了為什么要加一個新的過濾。

下面的注釋翻譯如下,這個解釋已經(jīng)很清楚了:


通常,我們將條件的full sort key作為鍵存儲在哈希表中。但是,如果字符串很長,或者我們有一個 PAD SPACE 排序規(guī)則,則可能導(dǎo)致排序鍵很大。如果我們檢測到這種情況可能發(fā)生在最壞的情況下,我們只會在鍵中存儲哈希值(因此我們對哈希值進(jìn)行哈希處理)。如果是這樣,我們必須事后重新檢查,以防范哈希沖突。

// Normally, we store the full sort key for the condition as key in the hash
  // table. However, if the string is very long, or we have a PAD SPACE
  // collation, this could result in huge sort keys. If we detect that this
  // could happen in the worst case, we store just a hash in the key instead (so
  // we hash the hash). If so, we have to do a recheck afterwards, in order to
  // guard against hash collisions.
  bool m_store_full_sort_key;

繼續(xù)看生成 key 的代碼可以發(fā)現(xiàn),如果是長度超過1024的字段,會通過append_hash_for_string_value先把超長 key 轉(zhuǎn)為 hash 值,所以才有上面解釋里面的儲存 hash 值的 hash 值的說法。

static bool extract_value_for_hash_join() {
switch (comparator->get_compare_type()) {
    case STRING_RESULT: {
      if (join_condition.store_full_sort_key()) { 這里代表長度沒有超過1024
        return append_string_value(
            comparand, comparator->cmp_collation.collation,
            join_condition.max_character_length(),
            (thd->variables.sql_mode & MODE_PAD_CHAR_TO_FULL_LENGTH) > 0,
            is_multi_column_key, join_key_buffer);
      } else { 這里代表長度超過1024
        return append_hash_for_string_value(
            comparand, comparator->cmp_collation.collation, join_key_buffer);
      }
    }
}

三、相關(guān)計算公式

判斷公式:
cs->coll->strnxfrmlen(cs, cs->mbmaxlen * m_max_character_length) > 1024
其中cs為字符集,cs->mbmaxlen為字符集的最大長度,m_max_character_length為字段長度
以上公式為真的話就在hashjoin外面另外套一層過濾器FilterIterator。

部分字符集和 Hash Join 內(nèi)部長度的計算公式表:

字符集

計算公式

說明

utf8mb4

((len + 3) / 4) * 2

其中4為字符集的最長長度

utf8mb3

((len + 2) / 3) * 2

其中3為字符集的最長長度

unicode_full_bin

((len + 3) / cs->mbmaxlen) * 3

cs->mbmaxlen為字符集的最長長度


注:表里的len=cs->mbmaxlen * m_max_character_length,其中cs為字符集,cs->mbmaxlen為字符集的最大長度,m_max_character_length為字段長度

這里我們舉一個例子計算一下:

假設(shè)有一個字段是c2 varchar(300),字符集是my_charset_utf8mb4_0900_ai_ci,找到utf8mb4_0900相關(guān)的計算函數(shù)如下:
static size_t my_strnxfrmlen_uca_900(const CHARSET_INFO *cs, size_t len) {
constsize_t num_codepoints = (len + 3) / 4;
constsize_t max_num_weights_per_level = num_codepoints * 8;
size_t max_num_weights = max_num_weights_per_level * cs->levels_for_compare;
if (cs->coll_param && cs->coll_param->reorder_param) {
    max_num_weights += max_num_weights_per_level;
  }
return (max_num_weights + (cs->levels_for_compare - 1)) * sizeof(uint16_t);
}

因此strnxfrmlen的計算公式就是:
num_codepoints = (300 * 4 + 3 ) / 4 = 300;
max_num_weights_per_level = num_codepoints * 8 = 2400
max_num_weights = max_num_weights_per_level * cs->levels_for_compare = 2400 * 1 = 2400
strnxfrmlen = (max_num_weights + (cs->levels_for_compare - 1)) * sizeof(uint16_t) = (2400 + 1-1 )) * 2= 4800
最后由于4800大于1024,因此執(zhí)行計劃需要在hashjoin外面另外套一層過濾器FilterIterator。
從上面的計算過程可以看出,如果不想套一層過濾器,那么varchar長度最大只能設(shè)置為64.

四、問題總結(jié)

通過以上分析我們可以發(fā)現(xiàn),執(zhí)行 Hash Join 的時候,連接條件的字段字符集和長度不一樣的時候,最后的執(zhí)行計劃結(jié)果也不一樣。究其原因是因為如果字段過長,hash 表只儲存 key 的 hash 值,這樣必須事后重新檢查,以防范哈希沖突。所以如果連接字段過長(比如 my_charset_utf8mb4_0900_ai_ci 字符集的情況下,varchar長度超過64),會比短字段(比如小于64長度)消耗更多資源和內(nèi)存用來做查詢,因此在實際使用中,應(yīng)該避免使用過長的字段進(jìn)行 Hash Join 連接。

責(zé)任編輯:武曉燕 來源: GreatSQL社區(qū)
相關(guān)推薦

2022-12-29 08:22:32

JOINCPU系統(tǒng)

2017-09-22 11:01:00

Oracle數(shù)據(jù)庫中直方圖

2011-09-14 17:03:17

數(shù)據(jù)庫執(zhí)行計劃解析

2021-03-17 09:35:51

MySQL數(shù)據(jù)庫explain

2023-09-21 10:55:51

MysqlSQL語句

2015-04-22 14:17:45

SQL SERVERMSSQL SERVE緩沖區(qū)

2021-05-28 10:46:36

MySQL執(zhí)行計劃

2009-11-10 16:00:05

Oracle執(zhí)行計劃

2020-09-15 08:44:57

MySQL慢日志SQL

2022-08-15 15:09:26

SQL數(shù)據(jù)庫MySQL

2010-04-16 09:27:18

Ocacle執(zhí)行計劃

2024-09-12 15:16:14

2009-11-13 16:28:02

Oracle生成執(zhí)行計

2022-08-08 08:03:44

MySQL數(shù)據(jù)庫CBO

2010-08-18 10:52:36

DB2執(zhí)行計劃顯示工具

2022-02-15 07:36:21

SQLEXPLAIN數(shù)據(jù)庫

2021-09-07 10:43:25

EverDB分布式執(zhí)行

2021-02-20 08:40:19

HiveExplain底層

2021-04-24 12:01:08

MySQL數(shù)據(jù)庫Mysql執(zhí)行計劃

2009-11-18 17:05:47

捕獲Oracle SQ
點贊
收藏

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

亚洲国产一区自拍| 精品亚洲欧美日韩| 欧美亚洲日本精品| 亚洲柠檬福利资源导航| 男人添女人下部视频免费| 亚洲看片免费| 99久久无色码| 97久久夜色精品国产| 欧美性视频精品| 在这里有精品| 另类视频在线观看| 欧美激情不卡| 一区二区日韩精品| 国产美女一区视频| 91麻豆精品国产91久久久久久久久 | 法国空姐在线观看免费| 欧美在线不卡| 成人a免费视频| 亚洲人亚洲人色久| 亚洲日本理论电影| 成人午夜在线视频一区| 国产色噜噜噜91在线精品 | 91在线一区二区| 国产精品永久免费观看| 98在线视频| 岛国一区二区在线观看| 国产精品网址在线| 91偷拍一区二区三区精品| 欧美激情视频一区二区| 午夜影院久久久| 亚洲欧洲一二区| 国产精品久久波多野结衣| 性欧美xxxx免费岛国不卡电影| 亚洲精品久久7777| 九色在线网站| 91成人国产精品| 免费看污污网站| 国产在线精品一区二区三区不卡| 奇米精品一区二区三区在线观看一 | 国产精品不卡| 999精品视频一区二区三区| 一本色道久久88亚洲精品综合| 欧美日韩午夜剧场| 午夜视频国产| 欧美性xxxx极品高清hd直播| 99视频在线观看地址| 日韩三级av在线播放| 欧美日韩不卡| 久久久久女教师免费一区| 亚洲三级性片| 国产精品12| 国产精品一区二区91| 成人看片app| 精品一区91| 国产区精品视频| 日韩中文字幕麻豆| 国产黄视频在线| 亚洲一区在线观看免费 | 四季av一区二区三区免费观看| 国产免费一区| 国产网红在线观看| 国产亚洲精品久久久久动| 欧美黑人做爰爽爽爽| 欧美一卡2卡3卡4卡无卡免费观看水多多| 激情综合色综合久久| 成人3d动漫网站| 欧美一区欧美二区| 亚洲开心激情| 久久福利电影| 国产精品理伦片| 粗大黑人巨茎大战欧美成人| 久久一区视频| 麻豆av免费在线| 欧美日韩三级一区二区| 一区二区网站| 日本黄网免费一区二区精品| 最近中文字幕一区二区三区| 激情网站在线| 国产精品色视频| 国产.欧美.日韩| 国产黄色片在线观看| 欧美大肥婆大肥bbbbb| 国产精品日韩久久久| 99爱视频在线观看| 欧美黄免费看| 成 年 人 黄 色 大 片大 全| 色视频欧美一区二区三区| 老司机精品视频网| 区一区二区三区中文字幕| 亚洲精品成人少妇| 国产成人精选| 日日夜夜精品网站| 狠狠色狠狠色综合日日五| 免费欧美网站| 日本一区二区三不卡| 一区二区三区欧美日| 青青草原网站在线观看| 性久久久久久久| 国产精品igao视频网网址不卡日韩 | 精品一区二区久久| 成人高清网站| 欧美性视频精品| 成人黄页在线观看| 五月天激情在线| **亚洲第一综合导航网站| 成人在线观看免费视频| 国产精品一区二区三区在线播放| 国精产品一区一区三区mba视频| 国产高清美女一级毛片久久| 欧洲精品久久久| 国产欧美一区二区三区网站| 国产精品亲子伦av一区二区三区| 视频一区视频二区视频三区视频四区国产 | 成人妇女淫片aaaa视频| 中文字幕亚洲精品在线观看| 国产成人视屏| 亚洲国产精品免费| 91精品电影| 久草在线在线| 日本久久91av| 成人免费一区二区三区视频 | 欧美性大战久久久久久久蜜臀| 欧美日韩伦理| 日韩加勒比系列| 456亚洲影院| 91吃瓜在线观看| 鲁丝片一区二区三区| 精品国产福利视频| 不卡在线一区二区| 粗大的内捧猛烈进出在线视频| 国产成一区二区| 亚洲精品中文字幕乱码三区| 国产99亚洲| 资源视频在线播放免费| 成人深夜直播免费观看| 日韩欧美999| 全网国产福利在线播放| 久久中文字幕在线| 久久久久久99久久久精品网站| 精品国产黄a∨片高清在线| 国产3p露脸普通话对白| 欧美成人免费一级人片100| 国产天堂亚洲国产碰碰| 琪琪久久久久日韩精品| 嫩模私拍啪啪| 亚洲伊人一本大道中文字幕| 一本久久a久久免费精品不卡| 68国产成人综合久久精品| 国产香蕉在线| 三区精品视频观看| 亚洲欧美色婷婷| 国产黄色小视频在线| 一本久久a久久精品vr综合| 亚洲天堂av在线播放| 97久久精品人人做人人爽| 成人高潮a毛片免费观看网站| 美女激情网站| 国产亚洲欧美一区二区| 精品国产乱码久久久久久蜜臀 | 91精品国产91| 香蕉成人伊视频在线观看| www.91av| 日本精品视频在线| 色婷婷激情一区二区三区| 久久亚洲欧美| www.国产精品| 成人永久免费网站| 电影午夜精品一区二区三区 | 欧美成人亚洲成人日韩成人| 一区二区欧美国产| 亚洲国产精品一区制服丝袜| 国产高清自拍99| 亚洲国产精品成人精品| 日本少妇一区二区| 粉嫩的18在线观看极品精品| 中文字幕欧美一区二区| 日韩欧美精品久久| 久久久久久久爱| 欧美视频中文字幕| 9色porny自拍视频一区二区| 狠狠操综合网| 波多野结衣乳巨码无在线观看| 日韩欧美xxxx| 国产在线精品二区| 不卡毛片在线看| 欧美男同性恋视频网站| 欧美大片高清| 中文字幕在线导航| 久久青青草综合| 久久久久久久97| 日韩女同互慰一区二区| 国产精品久久久久久久久免费樱桃 | 国产精品高潮呻吟久久久久| 4p变态网欧美系列| 欧美蜜桃一区二区三区 | 精品视频在线观看一区二区| 国产精品成人v| 亚洲精品视频二区| 欧美日韩一区免费| 久久久久久综合|