多表JOIN操作是複雜分析查詢中的常見效能瓶頸。PolarDB MySQL版的串連(JOIN)消除功能,可在查詢最佳化階段識別並移除不必要的JOIN操作,以簡化執行計畫、減少I/O和計算量,提升查詢效能。
適用範圍
產品系列:叢集版、標準版。
核心版本:MySQL 8.0.2,且修訂版本需為8.0.2.2.31.1及以上版本。
開啟串連(JOIN)消除
通過設定join_elimination_mode參數來控制此最佳化功能的行為。
PolarDB叢集參數在控制台與會話中修改方式存在差異,詳細區別如下:
在PolarDB控制台上修改:
相容性說明:部分叢集參數在PolarDB控制台上均已添加MySQL設定檔的相容性首碼loose_。
操作方法:找到並修改這些帶
loose_首碼的參數。
在資料庫會話中修改(使用命令列或用戶端):
操作方法:當您串連到資料庫,使用
SET命令修改參數時,請去掉loose_首碼,直接使用參數的原始名稱進行修改。
參數名稱 | 層級 | 描述 |
| Global/Session | 控制該功能的主開關。取值範圍如下:
|
最佳化情境樣本
當前功能支援在以下六種情境中自動進行串連消除。
情境一:消除ON條件為常假的左串連(LEFT JOIN + FALSE COND)
LEFT JOIN的ON條件恒為FALSE時,該串連無意義。最佳化器會移除內表,並將其所有列替換為NULL。
-- 最佳化前
SELECT ..., ti1.*, ti2.*, ... FROM ... LEFT JOIN (ti1, ti2, ...) ON FALSE;
-- 最佳化後
SELECT ..., NULL, NULL, ... FROM ...;消除條件
LEFT JOIN的ON條件恒為FALSE的運算式(例如1=0或FALSE)。
情境樣本
準備環境。
DROP TABLE IF EXISTS orders; CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE ); DROP TABLE IF EXISTS customers; CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(100) ); INSERT INTO orders VALUES (1, 101, '2023-10-01');最佳化前:關閉JOIN消除,執行一個
ON FALSE的查詢。SET SESSION join_elimination_mode = 'OFF'; EXPLAIN SELECT o.*, c.* FROM orders o LEFT JOIN customers c ON FALSE;SHOW warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `testdb`.`o`.`order_id` AS `order_id`,`testdb`.`o`.`customer_id` AS `customer_id`,`testdb`.`o`.`order_date` AS `order_date`,`testdb`.`c`.`customer_id` AS `customer_id`,`testdb`.`c`.`customer_name` AS `customer_name` from `testdb`.`orders` `o` left join `testdb`.`customers` `c` on(false) where true | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+最佳化後:開啟JOIN消除,執行相同的查詢。
SET SESSION join_elimination_mode = 'ON'; EXPLAIN SELECT o.*, c.* FROM orders o LEFT JOIN customers c ON FALSE;SHOW warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `testdb`.`o`.`order_id` AS `order_id`,`testdb`.`o`.`customer_id` AS `customer_id`,`testdb`.`o`.`order_date` AS `order_date`,NULL AS `customer_id`,NULL AS `customer_name` from `testdb`.`orders` `o` | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+執行計畫顯示
customers表的訪問被消除。
情境二:消除不影響結果的唯一鍵左串連 (LEFT JOIN + UNIQUE)
當左串連的內表未在查詢的其他部分被引用,且串連操作不改變外表的行數時,對內表的串連可以被安全地消除。
-- 最佳化前
SELECT to1.*, to2.*, ..., tom.* FROM to1, to2, ..., tom LEFT JOIN (ti1, ti2, ..., tin) ON cond_on WHERE cond_where ...
-- 最佳化後
SELECT to1.*, to2.*, ..., tom.* FROM to1, to2, ..., tom WHERE cond_where ...消除條件
內表未被引用:在
LEFT JOIN及其ON條件以外的地方,都沒有引用內表(即ti1, ti2, ..., tin)的任何列。串連不影響外表基數或結果:需滿足以下條件之一。
串連具有唯一性:對於外表的每一行,經過左串連後有且僅能匹配到內表的一行資料。
串連產生的重複行對結果無影響:即使串連導致外表的行被複製,但查詢的後續操作會消除這些重複行的影響。常見情況包括:
串連位於一個
EXISTS或IN子句(即半串連,SEMI JOIN)中。子查詢中包含了
GROUP BY、LIMIT或視窗函數等限制條件。遞迴使用上述兩項。
情境三:消除自串連(SELF JOIN)
當一個基表(或其派生表)與自身進行內串連(INNER JOIN)時,相當於用兩份相同或相似的資料集進行匹配。如果滿足特定條件,最佳化器可以識別出其中一份資料是多餘的,並將其從執行計畫中消除,從而避免對同一份資料的重複讀取。
基表JOIN基表:
-- 最佳化前 SELECT target.*, source.* FROM t1 as target JOIN t1 as source WHERE target.uk = source.uk; -- 最佳化後 SELECT source.*, source.* FROM source WHERE source.uk = source.uk;派生表JOIN派生表:
-- 最佳化前 SELECT target.*, source.* FROM (SELECT * FROM t1) target JOIN (SELECT * FROM t1 WHERE t1.a > 1) source WHERE target.uk = source.uk; -- 最佳化後 SELECT source.*, source.* FROM (SELECT * FROM t1 WHERE t1.a > 1) source WHERE source.uk = source.uk;基表JOIN派生表:
-- 最佳化前 SELECT target.*, source.* FROM t1 target JOIN (SELECT * FROM t1 WHERE t1.a > 1) source WHERE target.uk = source.uk; -- 最佳化後 SELECT source.*, source.* FROM (SELECT * FROM t1 WHERE t1.a > 1) source WHERE source.uk = source.uk;
消除條件
子集關係:
source表的結果集需是target表結果集的子集。列可用性:查詢中所有被引用的
targe表的列,在source表中也需存在。簡單來說,被保留的source表需包含查詢所需的所有列。唯一鍵串連:
JOIN的串連條件需是基於target表中的一個唯一鍵(UNIQUE KEY)或主鍵(PRIMARY KEY)的等值比較。這個條件是關鍵,它保證了source表中的每一行最多隻能匹配到
target表中的一行。這確保了消除target表不會改變查詢結果的行數和邏輯。特殊情況:如果串連條件中沒有基於唯一鍵的等值比較,那麼只有當
target表的結果集本身只有0行或1行時,才可能進行消除。
情境四:消除半自串連(SELF SEMI JOIN)
當一個表與自身進行半串連(SEMI JOIN,通常表現為IN或EXISTS子句)時,如果滿足特定條件,子查詢中的表可以被消除,其條件會被合并到外層查詢。
有唯一列相等條件。
-- 最佳化前 SELECT source.* FROM t1 as source WHERE EXISTS (SELECT * FROM t1 as target WHERE source.uk = target.uk AND target.a > 1); -- 最佳化後 SELECT source.* FROM t1 as source WHERE source.uk = source.uk AND source.a > 1;無唯一列相等條件。
-- 最佳化前 SELECT source.* FROM t1 as source WHERE EXISTS (SELECT * FROM t1 as target WHERE source.a = target.a); -- 最佳化後 SELECT source.* FROM t1 as source WHERE source.a = source.a;
消除條件
子集關係:
source表的結果集需是target表結果集的子集。列可用性:查詢中所有被引用的
targe表的列,在source表中也需存在。簡單來說,被保留的source表需包含查詢所需的所有列。還需滿足以下條件之一:
唯一鍵串連:
JOIN的串連條件需是基於target表中的一個唯一鍵(UNIQUE KEY)或主鍵(PRIMARY KEY)的等值比較。這個條件是關鍵,它保證了source表中的每一行最多隻能匹配到
target表中的一行。這確保了消除target表不會改變查詢結果的行數和邏輯。特殊情況:如果串連條件中沒有基於唯一鍵的等值比較,那麼只有當
target表的結果集本身只有0行或1行時,才可能進行消除。
所有串連條件均為等值(即
)串連,且與其他添加為 AND串連。
情境五:消除外鍵串連(FOREIGN KEY JOIN)
如果兩表間存在外鍵約束,JOIN條件為該外鍵關係,且查詢只引用子表(含外鍵的表)的列,則對父表的JOIN可被消除。外鍵約束保證了子表中的行在父表中必有對應行。
CREATE TABLE target (a int primary key);
CREATE TABLE source (a int, foreign key (a) references target(a));
-- 最佳化前
SELECT target.a, source.a FROM target, source WHERE target.a = source.a;
-- 最佳化後
SELECT source.a, source.a FROM source WHERE source.a = source.a;消除條件
父表引用可替代:查詢所引用的所有父表列,都可以通過子表的對應外鍵列來替代(通常意味著只引用了父表的串連鍵本身)。
串連條件基於外鍵關係:
JOIN的條件是子表的外鍵列與父表的主鍵/唯一鍵列的等值比較。外鍵非空保證:子表中的外鍵列被定義為
NOT NULL,這確保了子表中的每一行在父表中都有確切的對應行。
情境六:消除外鍵半串連(FOREIGN KEY SEMI JOIN)
當使用EXISTS或IN子句通過外鍵關係檢查父表是否存在對應記錄時,該檢查是多餘的,因為外鍵約束已保證記錄存在。因此,該SEMI JOIN子查詢可被消除。
CREATE TABLE target (a int primary key);
CREATE TABLE source (a int, foreign key (a) references target(a));
-- 最佳化前
SELECT source.a FROM source WHERE EXISTS(SELECT * FROM target WHERE target.a = source.a);
-- 最佳化後
SELECT source.a FROM source WHERE source.a = source.a;消除條件
父表引用可替代:查詢所引用的所有父表列,都可以通過子表的對應外鍵列來替代(通常意味著只引用了父表的串連鍵本身)。
串連條件基於外鍵關係:
JOIN的條件是子表的外鍵列與父表的主鍵/唯一鍵列的等值比較。外鍵非空保證:子表中的外鍵列被定義為
NOT NULL,這確保了子表中的每一行在父表中都有確切的對應行。