通過主外鍵約束消除多余的JOIN
本文介紹如何使用主鍵(PRIMARY KEY)與外鍵(FOREIGN KEY)之間的約束關系來優化查詢計劃,消除多余的JOIN操作。
前提條件
AnalyticDB for MySQL集群內核版本需為3.1.10或以上。
查看企業版或湖倉版集群的內核版本,請執行SELECT adb_version();
。如需升級內核版本,請聯系技術支持。
功能介紹
在數據庫中,通常在大型數據表中保存大量數據,同時還需要進行復雜的查詢和分析操作。使用JOIN連接可以將兩個或多個表中的行組合在一起。但是,JOIN連接操作可能會導致性能下降,因此需要優化查詢性能。JOIN消除是一種優化查詢性能的技術。其中,根據主外鍵約束信息進行JOIN消除是一種具體的優化方法。這些約束提供了關于表與表之間關系的信息,可以用來減少不必要的JOIN操作,減少查詢時間,提高數據庫性能。
在AnalyticDB for MySQL中,支持通過Hint/*+ PK_FK_DEPENDENCY_ENABLED*/
來開啟或關閉使用外鍵約束信息消除多余JOIN操作的功能,其中:
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
:開啟獲取PK-FK信息功能。開啟后,系統在應用JOIN消除規則的時候會獲取到表中PK-FK信息,并根據此信息進行JOIN消除。/*+ PK_FK_DEPENDENCY_ENABLED = false*/
:關閉獲取PK-FK信息功能。關閉后,系統無法根據PK-FK信息應用JOIN消除規則。
PK(PRIMARY KEY):主鍵
FK(FOREIGN KEY):外鍵
操作流程
聲明FOREIGN KEY
新建表時,通過CREATE TABLE創建FOREIGN KEY。
對于已存在的表,通過ALTER TABLE創建或刪除FOREIGN KEY。
不支持多個列作為外鍵,例如:
FOREIGN KEY (sr_item_sk, sr_ticket_number) REFERENCES store_sales(ss_item_sk,d_date_sk)
。您需要自行確保主鍵和外鍵之間的數據約束關系。AnalyticDB for MySQL不會進行數據的約束檢查。
DROP TABLE
命令會自動刪除外鍵,AnalyticDB for MySQL不會進行檢驗和報錯。外表不支持創建外鍵約束。
通過CREATE TABLE創建FOREIGN KEY
語法
詳細的語法請參見CREATE TABLE。
示例
創建名為
db
的數據庫,并在該數據庫下創建一個名為item
的表,主鍵為i_item_sk
,示例如下:CREATE DATABASE db; USE db; CREATE TABLE item ( i_item_sk bigint NOT NULL, i_current_price bigint, PRIMARY KEY(i_item_sk) ) DISTRIBUTED BY HASH(i_item_sk);
通過CREATE TABLE創建FOREIGN KEY。
在同一數據庫中創建FOREIGN KEY。
在
db
數據庫中創建一個名為store_returns
的表,通過使用外鍵語法FOREIGN KEY
將sr_item_sk
列和item
表的主鍵列i_item_sk
關聯起來。示例如下:CREATE TABLE store_returns ( sr_sale_id bigint, sr_store_sk bigint, sr_item_sk bigint NOT NULL, FOREIGN KEY (sr_item_sk) REFERENCES item (i_item_sk) );
說明由于缺少參數
symbol
,解析器將會使用外鍵列名自動補充約束名為sr_item_sk_fk
。跨數據庫創建FOREIGN KEY。
創建名為
db2
的數據庫,并在該數據庫中創建名為store_sales
的表,創建外鍵列ss_item_sk
。示例如下:CREATE DATABASE db2; USE db2; CREATE TABLE store_sales ( ss_sale_id bigint, ss_store_sk bigint, ss_item_sk bigint not null, CONSTRAINT fk_constraint FOREIGN KEY (ss_item_sk) REFERENCES db.item (i_item_sk) );
通過CREATE TABLE語句為同一個表聲明多個FOREIGN KEY
示例:
分別創建名為
customer
和vendor
的表。USE db; CREATE TABLE customer ( i_customer_sk bigint NOT NULL, i_current_price bigint, PRIMARY KEY(i_customer_sk) ) DISTRIBUTED BY HASH(i_customer_sk); CREATE TABLE vendor ( id bigint primary key, name varchar(5) not null );
創建名為
store_product
的表,將sr_sale_id
列和vendor
表的主鍵列id
關聯起來;將sr_customer_sk
列和customer
表的主鍵列i_customer_sk
關聯起來。CREATE TABLE store_product ( sr_sale_id bigint, sr_store_sk bigint, sr_customer_sk bigint NOT NULL, FOREIGN KEY (sr_sale_id) REFERENCES vendor (id), FOREIGN KEY (sr_customer_sk) REFERENCES customer (i_customer_sk) );
查看已創建的FOREIGN KEY
通過SHOW CREATE TABLE
查看已創建的FOREIGN KEY。
示例:
USE db;
SHOW CREATE TABLE store_returns;
返回結果:
-- 結果(省略其他語句):
-- CONSTRAINT `sr_item_sk_fk` FOREIGN KEY (`sr_item_sk`) REFERENCES `db`.`item`(`i_item_sk`)
通過ALTER TABLE創建或刪除FOREIGN KEY
語法
詳細的語法請參見ALTER TABLE。
示例:
本示例中,數據庫為db
,需要添加外鍵的表為store_returns
,主表為item
。
刪除表
store_returns
的外鍵。USE db; ALTER TABLE store_returns DROP FOREIGN KEY sr_item_sk_fk;
為表
store_returns
添加外鍵。USE db; ALTER TABLE store_returns ADD CONSTRAINT sr_item_fk FOREIGN KEY (sr_item_sk) REFERENCES item (i_item_sk);
通過ALTER TABLE為同一個表聲明多個FOREIGN KEY
ALTER TABLE語句一次只能為一個表添加一個外鍵,當需要為同一個表添加多個外鍵時,可以使用多條ALTER TABLE語句。
示例:
在名稱為
db2
的數據庫中創建一個store
表:USE db2; CREATE TABLE store ( id bigint primary key, name varchar(5) not null );
為
db
數據庫的store_returns
表添加外鍵:ALTER TABLE db.store_returns ADD FOREIGN KEY (sr_store_sk) REFERENCES store(id);
應用JOIN消除規則
JOIN消除是在JOIN連接中,只需要查詢一個表中的數據時,可以利用主外鍵約束消除不必要的JOIN關系,以簡化查詢計劃并提升查詢性能。常見應用場景如下:
同一數據庫中的雙表連接查詢
例如,查詢在store_returns
和item
表之間執行JOIN操作,但僅選擇輸出store_returns
中的列。
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
EXPLAIN
SELECT
s.sr_sale_id,
s.sr_store_sk,
s.sr_item_sk
FROM
store_returns s,
item
WHERE
sr_item_sk = i_item_sk;
返回結果:
+---------------+
| Plan Summary |
+---------------+
1- Output[ Query plan ] {Est rowCount: 1.0}
2 -> Exchange[GATHER] {Est rowCount: 1.0}
3 - TableScan {table: store_returns, Est rowCount: 1.0}
執行計劃中沒有INNER JOIN算子,說明優化器能夠利用sr_item_sk
和i_item_sk
的約束安全地消除表store_returns
與表item
的連接。
跨數據庫連接查詢
例如,涉及數據庫db
的item
表和數據庫db2
的store_sales
表連接的查詢:
USE db2;
-- 示例查詢語句
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
EXPLAIN
SELECT
s.ss_sale_id,
s.ss_item_sk
FROM
store_sales s,
db.item
WHERE
ss_item_sk = i_item_sk;
返回結果:
+---------------+
| Plan Summary |
+---------------+
1- Output[ Query plan ] {Est rowCount: 1.0}
2 -> Exchange[GATHER] {Est rowCount: 1.0}
3 - TableScan {table: store_sales, Est rowCount: 1.0}
多表連接查詢
例如,涉及表store_returns
、表item
以及表store
連接的查詢:
USE db;
-- 示例查詢語句
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
EXPLAIN
SELECT
s.sr_sale_id,
s.sr_store_sk,
s.sr_item_sk
FROM
store_returns s,
item,
db2.store
WHERE
sr_item_sk = i_item_sk
AND sr_store_sk = id;
返回結果:
+---------------+
| Plan Summary |
+---------------+
1- Output[ Query plan ] {Est rowCount: 1.0}
2 -> Exchange[GATHER] {Est rowCount: 1.0}
3 - TableScan {table: store_returns, Est rowCount: 1.0}
視圖
由于視圖的設計,可能會使用到兩個或以上表的JOIN,但是將視圖內容應用到其他查詢語句中時,該視圖就有可能包含了無用的信息,從而允許優化器消除無用的JOIN。例如創建一個包含表store_returns
和表item
信息的視圖結構:
CREATE VIEW sr_item_v AS
SELECT
s.sr_store_sk AS store_name,
s.sr_sale_id AS sale_id,
s.sr_item_sk AS sr_item_id,
item.i_current_price AS item_price,
item.i_item_sk as item_id
FROM
store_returns s,
item
WHERE
sr_item_sk = i_item_sk;
在查詢中不引用item
表中的item_price
列時,優化器可以優化不掃描item
表。例如:
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
EXPLAIN
SELECT store_name, sr_item_id, sale_id
FROM sr_item_v;
返回結果:
+---------------+
| Plan Summary |
+---------------+
1- Output[ Query plan ] {Est rowCount: 1.0}
2 -> Exchange[GATHER] {Est rowCount: 1.0}
3 - TableScan {table: store_returns, Est rowCount: 1.0}
由于item.i_item_sk
和store_returns.sr_item_sk
之間存在外鍵約束,優化器知道可以將對item.i_item_sk
的引用替換為對store_returns.sr_item_sk
的引用。在這種情況下,優化器將查詢轉換為store_returns
表中的列,從而允許使用JOIN消除規則。
無法應用JOIN消除規則的情況
以上文的視圖結構為例,查詢使用到了item
表上的一些列,JOIN操作就是必需的。
-- 示例查詢語句
/*+ PK_FK_DEPENDENCY_ENABLED = true*/
EXPLAIN
SELECT store_name, sr_item_id, sale_id, item_price
FROM sr_item_v;
返回結果:
+---------------+
| Plan Summary |
+---------------+
1- Output[ Query plan ] {Est rowCount: 1.0}
2 -> Exchange[GATHER] {Est rowCount: 1.0}
3 -> InnerJoin[Hash Join] {Est rowCount: 1.0}
4 -> Project {Est rowCount: 1.0}
5 -> Exchange[REPARTITION] {Est rowCount: 1.0}
6 - TableScan {table: store_returns, Est rowCount: 1.0}
7 -> LocalExchange[HASH] {Est rowCount: 1.0}
8 -> ScanProject {table: item, Est rowCount: 1.0}
9 - TableScan {table: item, Est rowCount: 1.0}