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

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

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

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

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

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)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

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

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

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

// 調(diào)查代碼發(fā)現(xiàn)跟下面代碼的長度判斷有關(guān),如果計(jì)算出來的內(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í)行一次過濾,計(jì)算公式見下面三的表格
    if (cs->coll->strnxfrmlen(cs, cs->mbmaxlen * m_max_character_length) > 1024) { 
      m_store_full_sort_key = false;
    }
  }
}

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

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

字段長度

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表掃描第二張表的時(shí)候通過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í)行一次過濾

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

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


通常,我們將條件的full sort key作為鍵存儲在哈希表中。但是,如果字符串很長,或者我們有一個(gè) 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)計(jì)算公式

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

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

字符集

計(jì)算公式

說明

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為字段長度

這里我們舉一個(gè)例子計(jì)算一下:

假設(shè)有一個(gè)字段是c2 varchar(300),字符集是my_charset_utf8mb4_0900_ai_ci,找到utf8mb4_0900相關(guān)的計(jì)算函數(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的計(jì)算公式就是:
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í)行計(jì)劃需要在hashjoin外面另外套一層過濾器FilterIterator。
從上面的計(jì)算過程可以看出,如果不想套一層過濾器,那么varchar長度最大只能設(shè)置為64.

四、問題總結(jié)

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

2015-04-22 14:17:45

SQL SERVERMSSQL SERVE緩沖區(qū)

2023-09-21 10:55:51

MysqlSQL語句

2021-03-17 09:35:51

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

2021-05-28 10:46:36

MySQL執(zhí)行計(jì)劃

2020-09-15 08:44:57

MySQL慢日志SQL

2009-11-10 16:00:05

Oracle執(zhí)行計(jì)劃

2010-04-16 09:27:18

Ocacle執(zhí)行計(jì)劃

2022-08-15 15:09:26

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

2022-08-08 08:03:44

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

2009-11-13 16:28:02

Oracle生成執(zhí)行計(jì)

2024-09-12 15:16:14

2010-08-18 10:52:36

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

2022-02-15 07:36:21

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

2021-02-20 08:40:19

HiveExplain底層

2021-04-24 12:01:08

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

2009-11-18 17:05:47

捕獲Oracle SQ

2021-09-07 10:43:25

EverDB分布式執(zhí)行
點(diǎn)贊
收藏

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