GreatSQL優化技巧:半連接(semijoin)優化

来源:https://www.cnblogs.com/greatsql/p/18139862
-Advertisement-
Play Games

何為半連接? 半連接是在GreatSQL內部採用的一種執行子查詢的方式,semi join不是語法關鍵字,不能像使用inner join、left join、right join這種語法關鍵字一樣提供給用戶來編寫SQL語句。 兩個表t1表和t2表進行半連接的含義是:對於t1表的某條記錄來說,我們只關 ...


何為半連接?

半連接是在GreatSQL內部採用的一種執行子查詢的方式,semi join不是語法關鍵字,不能像使用inner joinleft joinright join這種語法關鍵字一樣提供給用戶來編寫SQL語句。

兩個表t1表和t2表進行半連接的含義是:對於t1表的某條記錄來說,我們只關心在t2表中是否存在與之匹配的記錄,而不關心有多少條記錄與之匹配,最終的結果集中只保留t1表的記錄。

前面文章也提到過,含in、exists子查詢的語句通常會採用半連接方式執行查詢,但這不絕對,也有一些情況不適用半連接。比如:

(1)外查詢的where子句中,存在其他搜索條件使用OR操作符與IN子查詢的條件連接起來

(2)IN子查詢位於Select子句中

(3)IN子查詢中含有union的情況

(4)IN子查詢中含group by、having或聚合函數的情況

GreatSQL執行半連接的優化策略

本文實驗使用資料庫版本為GreatSQL 8.0.32-25。

創建兩張實驗表來說明。

greatsql> create table t1(
c1 varchar(30),
c2 int
);
greatsql> create table t2(
id int primary key,
c1 varchar(30),
key idx_c1(c1)
);
--插入幾條測試數據
greatsql> insert into t1 values('a',1);
greatsql> insert into t1 values('b',3);
greatsql> insert into t1 values('a',5);
greatsql> insert into t1 values('c',7);
greatsql> insert into t1 values('d',9);
greatsql> insert into t2 values(1,'a');
greatsql> insert into t2 values(2,'a');
greatsql> insert into t2 values(3,'b');
greatsql> insert into t2 values(4,'b');
greatsql> insert into t2 values(5,'c');
greatsql> insert into t2 values(6,'b');

GreatSQL執行半連接的方式大致有以下5種:

1.Table pullout(子查詢中的表上拉)

當子查詢的查詢列表處只有主鍵或者唯一索引列時,可以直接把子查詢中的表上拉到外層查詢的FROM子句中,並把子查詢的查詢條件合併到外層查詢的搜索條件中。所以選擇這種方式是有先決條件的,子查詢的查詢列表處必須只有主鍵或唯一索引列。有沒有選擇這種方式,可以通過執行explain展示計劃後,使用show warnings命令查看優化器改寫後的語句。

例如下麵這個語句:

select * from t1 where c2 in (select id from t2 where t2.c1='b');

這個語句種子查詢的id列是t2表的主鍵列,滿足這種方式的先決條件,看一下執行計劃。

greatsql> explain select * from t1 where c2 in (select id from t2 where t2.c1='b');
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys  | key    | key_len | ref   | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t2  | NULL       | ref  | PRIMARY,idx_c1 | idx_c1 | 123     | const |    3 |   100.00 | Using index                                |
|  1 | SIMPLE      | t1  | NULL       | ALL  | NULL           | NULL   | NULL    | NULL  |    4 |    25.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

greatsql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                            |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t2` join `test`.`t1` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

從warning信息可以看出,優化器改執行連接方式是,t1表與t2表通過內連接來關聯,原子查詢內部t2表的過濾條件放到了整個語句where條件的後面,原語句與優化器執行的語句之所以等價,是因為子查詢的查詢列id列是主鍵列,不會有重覆值,跟外表t1使用inner join連接後,不會造成關聯後結果集數據量的放大。一般情況下子查詢的查詢列表處只有主鍵或者唯一索引列時都會轉化為這種方式來執行。對於這種業務,無論開發者怎麼編寫SQL,使用inner join 也好,exists也好,最後優化器執行方式可能都是一樣的。

可以看一下將原語句改造為inner join 與 exists語句的執行計劃,是不是都是一樣的。

greatsql> explain select * from t1 where exists (select 1 from t2 where t2.id=t1.c2 and t2.c1='b');
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys  | key    | key_len | ref   | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t2  | NULL       | ref  | PRIMARY,idx_c1 | idx_c1 | 123     | const |    3 |   100.00 | Using index                                |
|  1 | SIMPLE      | t1  | NULL       | ALL  | NULL           | NULL   | NULL    | NULL  |    4 |    25.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

greatsql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                            |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1276 | Field or reference 'test.t1.c2' of SELECT #2 was resolved in SELECT #1                                                                                                                           |
| Note  | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t2` join `test`.`t1` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

greatsql> explain select t1.* from t1 inner join t2 on t1.c2=t2.id where t2.c1='b';
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys  | key    | key_len | ref   | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t2  | NULL       | ref  | PRIMARY,idx_c1 | idx_c1 | 123     | const |    3 |   100.00 | Using index                                |
|  1 | SIMPLE      | t1  | NULL       | ALL  | NULL           | NULL   | NULL    | NULL  |    4 |    25.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.01 sec)

greatsql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                            |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` join `test`.`t2` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

這種執行方式本質上已經轉換為內連接了。

2.FirstMatch(首次匹配)

這種方式先取外層查詢的一條記錄,到子查詢的表中尋找符合匹配條件的記錄,如果能找到一條,則將外層查詢的記錄放入到最終結果集中並且停止查找匹配更多的記錄,如果找不到,則把該外層查詢的記錄丟棄掉,然後再開始取下一條外層查詢中的記錄,這個過程一直持續到外層查詢獲取不到記錄為止。

看一個簡單語句的執行計劃

select * from t1 where c1 in (select c1 from t2);
greatsql> explain select * from t1 where c1 in (select c1 from t2);
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+
| id | select_type | table | partitions | type | possible_keys | key    | key_len | ref          | rows | filtered | Extra                         |
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+
|  1 | SIMPLE      | t1  | NULL       | ALL  | NULL          | NULL   | NULL    | NULL         |    4 |   100.00 | Using where                   |
|  1 | SIMPLE      | t2  | NULL       | ref  | idx_c1        | idx_c1 | 123     | test.t1.c1 |    2 |   100.00 | Using index; FirstMatch(t1) |
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+
2 rows in set, 1 warning (0.01 sec)

greatsql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                  |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t2`.`c1` = `test`.`t1`.`c1`) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

從warning信息可以看到 semi join 的字樣,優化器使用半連接方式執行的子查詢。從執行計劃可以看到 extra 列有FirstMatch(t1) 的字樣,表示對t1表外查詢傳入的每個c1值在t2表上都進行了首次匹配,這種方式也是我最初理解的in子查詢的含義,只關心有無匹配上,不關心匹配上多少。

3.LooseScan(鬆散掃描)

LooseScan是使用子查詢的查詢列上的索引,只針對相同索引列值的第一條記錄,去外查詢找對應的記錄。使用了這種優化方式的半連接,在explain的計劃的Extra列會有LooseScan字樣。

還是上面的語句,使用semijoin的hint干涉優化器,使其選擇LooseScan的優化策略。

select /*+ semijoin(@subq1 loosescan) */  * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 );
greatsql> explain select /*+ semijoin(@subq1 loosescan) */  * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 );            
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t2  | NULL       | index | idx_c1        | idx_c1 | 123     | NULL |    6 |    50.00 | Using index; LooseScan                     |
|  1 | SIMPLE      | t1  | NULL       | ALL   | NULL          | NULL   | NULL    | NULL |    5 |    20.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.01 sec)

greatsql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                      |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` LOOSESCAN) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t1`.`c1` = `test`.`t2`.`c1`) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

從執行計劃可以看出,子查詢的表t2作為驅動表,t2表的c1列上有索引,對錶t2進行訪問時,使用其c1列的索引,對相同的索引列值只取第一條記錄去t1表中找對應記錄,將所有外查詢表t1對應的記錄都加入到最終結果集,可以理解為對子查詢t2表的索引掃描方式是跳躍式的。

4.Duplicate Weedout重覆值消除

這種方式是藉助臨時表來消除重覆值,explain展示計劃時,在extra列會出現Start temporaryEnd temporary的字樣。

還是上面的語句,我們使用semijoin的hint干涉優化器,使其選擇dupsweedout優化策略。

greatsql> explain select /*+ semijoin(@subq1 dupsweedout)*/ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2);
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key    | key_len | ref          | rows | filtered | Extra                                       |
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+
|  1 | SIMPLE      | t1  | NULL       | ALL  | NULL          | NULL   | NULL    | NULL         |    4 |   100.00 | Using where                                 |
|  1 | SIMPLE      | t2  | NULL       | ref  | idx_c1        | idx_c1 | 123     | test.t1.c1 |    2 |   100.00 | Using index; Start temporary; End temporary |
+----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

greatsql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                        |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` DUPSWEEDOUT) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t2`.`c1` = `test`.`t1`.`c1`) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

例如:t1表的記錄('b',3),可以匹配上t2表的兩條記錄(3,'b'),(4,'b'),為了消除關聯結果的重覆值,可以想象建立這樣一個臨時表:

create table tmp(rowid int primary key);

當把t1表的記錄加入到結果集時,先把這條記錄的rowid加入到臨時表中,如果添加成功,說明這條記錄並沒有加入到最後的結果集,如果添加失敗,則說明t1表的這條記錄已經加入到最終結果集了

個人感覺這種方式比其他方式效率低。

5.Semi-join Materialization(半連接物化)

先把IN 子句中的不相關子查詢進行物化,然後再將外層查詢的表與物化表進行連接。子查詢內部有分組聚合運算時通常會先進行物化處理。

還是上面的語句,使用semijoin的hint干涉優化器,使其選擇materialization的優化策略。

select /*+ semijoin(@subq1 materialization) */  * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 );
greatsql> explain select /*+ semijoin(@subq1 materialization) */  * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 );
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+
| id | select_type  | table       | partitions | type   | possible_keys       | key                 | key_len | ref          | rows | filtered | Extra       |
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+
|  1 | SIMPLE       | t1        | NULL       | ALL    | NULL                | NULL                | NULL    | NULL         |    5 |   100.00 | Using where |
|  1 | SIMPLE       | <subquery2> | NULL       | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 123     | test.t1.c1 |    1 |   100.00 | NULL        |
|  2 | MATERIALIZED | t2        | NULL       | index  | idx_c1              | idx_c1              | 123     | NULL         |    6 |   100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)

greatsql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                            |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` MATERIALIZATION) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`<subquery2>`.`c1` = `test`.`t1`.`c1`) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

從執行計劃可以看出,先對子查詢t2表做了物化表處理,物化表會生成自動索引<auto_distinct_key>,外查詢表t1再與物化表做Nest loop連接。

補充說明

對於上面的語句 select * from t1 where c1 in (select c1 from t2);,優化器預設選擇了firstmatch方式,其他方式都是使用hint來干涉的優化器的選擇,可以看到這個hint包含兩部分,一個是使用qb_name()給子查詢分配一個名稱,一個是使用semijoin([@query_block_name] [strategy]),指定子查詢塊使用半連接策略,可以指定多個策略。同時semijoin的優化策略的選擇還受優化開關參數optimize_switch的影響,該參數里有semijoin,loosescan,firstmatch,duplicateweedout的開關,預設都是開啟的,所以也可以使用優化開關來干涉優化器的選擇。

優化舉例

select count(*)
  from t1 a
 where substr(a.modifytime, 1, 8) = '20240301'
   and a.sospecnumber in
       (select a.sospecnumber
          from t1 a
         where substr(a.modifytime, 1, 8) < '20240301');

這條SQL只涉及一張表t1,表中數據200萬左右,modify_time為字元類型,存儲從2009年開始的時間串。看一下該表的索引情況。

greatsql> show index from t1;
+-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name         | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| t1    |          1 | idx_sospecnumber |            1 | SOSPECNUMBER | A         |         133 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| t1    |          1 | idx_modifytime   |            1 | MODIFYTIME   | A         |      634186 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.01 sec)

explain的執行計劃如下:

greatsql> explain
    -> select count(*)
    ->   from t1 a
    ->  where substr(a.modifytime, 1, 8) ='20240301'
    ->    and a.sospecnumber  in
    ->        (select a.sospecnumber
    ->           from t1 a
    ->          where substr(a.modifytime, 1, 8) < '20240301') ;
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+
| id | select_type  | table       | partitions | type   | possible_keys       | key                 | key_len | ref                 | rows    | filtered | Extra       |
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+
|  1 | SIMPLE       | a           | NULL       | ALL    | idx_sospecnumber    | NULL                | NULL    | NULL                | 2426414 |   100.00 | Using where |
|  1 | SIMPLE       | <subquery2> | NULL       | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 131     | test.a.SOSPECNUMBER |       1 |   100.00 | NULL        |
|  2 | MATERIALIZED | a           | NULL       | ALL    | idx_sospecnumber    | NULL                | NULL    | NULL                | 2426414 |   100.00 | Using where |
+----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)

優化器選擇的半連接優化策略是物化的方式。

explain analyze的實際計劃如下:

greatsql> explain analyze
    -> select count(*)
    ->   from t1 a
    ->  where substr(a.modifytime, 1, 8) ='20240301'
    ->    and a.sospecnumber  in
    ->        (select a.sospecnumber
    ->           from t1 a
    ->          where substr(a.modifytime, 1, 8) < '20240301') \G
*************************** 1. row ***************************
EXPLAIN: -> Aggregate: count(0)  (cost=1177497474524.58 rows=1) (actual time=4442.499..4442.500 rows=1 loops=1)
    -> Nested loop inner join  (cost=588748984584.98 rows=5887484899396) (actual time=4438.967..4442.408 rows=1346 loops=1)
        -> Filter: ((substr(a.MODIFYTIME,1,8) = '20240301') and (a.SOSPECNUMBER is not null))  (cost=252003.98 rows=2426414) (actual time=1550.096..1552.027 rows=1346 loops=1)
            -> Table scan on a  (cost=252003.98 rows=2426414) (actual time=0.050..1189.136 rows=2493198 loops=1)
        -> Single-row index lookup on <subquery2> using <auto_distinct_key> (sospecnumber=a.SOSPECNUMBER)  (cost=494645.48..494645.48 rows=1) (actual time=2.147..2.147 rows=1 loops=1346)
            -> Materialize with deduplication  (cost=494645.38..494645.38 rows=2426414) (actual time=2888.845..2888.845 rows=165 loops=1)
                -> Filter: (a.SOSPECNUMBER is not null)  (cost=252003.98 rows=2426414) (actual time=0.215..1927.315 rows=2487547 loops=1)
                    -> Filter: (substr(a.MODIFYTIME,1,8) < '20240301')  (cost=252003.98 rows=2426414) (actual time=0.214..1745.562 rows=2487547 loops=1)
                        -> Table scan on a  (cost=252003.98 rows=2426414) (actual time=0.211..1235.738 rows=2493198 loops=1)

1 row in set (4.45 sec)

優化分析:

這條SQL總體耗時4.45s,耗時主要分佈在兩處:

一處消耗在外表的查詢,對t1進行了全表掃描,回表過濾後剩餘1346行數據,耗時1552ms,此處雖然modifytime列有索引,但是因為在條件列上施加了substr函數,導致索引用不上,改為modifytime like '20240301%'的方式,也表示了查詢2024年3月1日的數據,同時用上了索引。

另一處消耗在子查詢的物化上,子查詢結果集有2487547行數據,表掃描、過濾、物化整個過程耗時約2888ms,對大結果集進行物化消耗比較大,同時IN子查詢的查詢列sospecnumber列上是有索引的,雖然選擇性不好,但是這個子查詢的含義是只需要判斷子查詢結果集中有無記錄能匹配上,而不關心匹配上多少條,所以這種情況採用first match方式比較好。

SQL改寫如下:

select /*+ semijoin(@subq firstmatch)*/
 count(*)
  from t1 a
 where a.modifytime like '20240301%'
   and a.sospecnumber in
       (select /*+ qb_name(subq)*/
         a.sospecnumber
          from t1 a
         where substr(a.modifytime, 1, 8) < '20240301')

改寫後執行計劃如下:

*************************** 1. row ***************************
EXPLAIN: -> Aggregate: count(0)  (cost=11052513.72 rows=1) (actual time=157.570..157.570 rows=1 loops=1)
    -> Nested loop semijoin  (cost=8596909.70 rows=24556040) (actual time=0.203..157.450 rows=1346 loops=1)
        -> Filter: (a.SOSPECNUMBER is not null)  (cost=606.05 rows=1346) (actual time=0.057..7.610 rows=1346 loops=1)
            -> Index range scan on a using idx_modifytime over ('20240301' <= MODIFYTIME <= '20240301????????????????????????????????????????????????'), with index condition: (a.MODIFYTIME like '20240301%')  (cost=606.05 rows=1346) (actual time=0.055..7.406 rows=1346 loops=1)
        -> Filter: (substr(a.MODIFYTIME,1,8) < '20240301')  (cost=83255911.06 rows=18244) (actual time=0.111..0.111 rows=1 loops=1346)
            -> Index lookup on a using idx_sospecnumber (SOSPECNUMBER=a.SOSPECNUMBER)  (cost=83255911.06 rows=18244) (actual time=0.111..0.111 rows=1 loops=1346)

1 row in set, 1 warning (0.16 sec)

改寫後耗時0.16s,性能提升近30倍,在對子查詢通過索引idx_sospecnumber搜索數據時,查到一條就會停止繼續搜索了。

結語

GreatSQL的 IN 子查詢適用於半連接時,優化器提供了5種優化策略:Table pullout、FirstMatch、LooseScan、Duplicate weedout、materialize。

一般外查詢表結果集小,子查詢結果集太大時,不希望通過物化這種方式來執行連接,因為物化表的代價太大,可能通過FirstMatch或者LooseScan很快就可以執行出結果了。那反之外查詢結果集大,子查詢結果集小時,通過物化表這種方式可能就會取得很好的效果。很多時候都不用過多干涉優化器做選擇,但是如果懂得原理,當優化器選錯的時候我們也可以通過hint來穩定計劃,讓SQL保持高效的執行。


Enjoy GreatSQL

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、安裝Docker Centos7.6-centos7.9 # 配置主機名: hostnamectl set-hostname master1 && bash #關閉防火牆 systemctl stop firewalld && systemctl disable firewalld #關閉ipt ...
  • Linux 的重要性不用我多說了吧,大多數互聯網公司,伺服器都是採用的Linux操作系統 Linux是一個主要通過命令行來進行管理的操作系統。 只有熟練掌握Linux核心命令,在使用起來我們才會得心應手 這裡給大家整理了Linux一些核心命令,掌握這些核心命令,工作中應該游刃有餘了 一、腦圖 二、詳 ...
  • Part1:sleep 實驗要求與提示 可以參考 user/echo.c, user/grep.c 和 user/rm.c 文件 如果用戶忘記傳遞參數,sleep 應該列印一條錯誤消息 命令行參數傳遞時為字元串,可以使用 atoi 函數將字元串轉為數字 使用系統調用 sleep,有關實現 sleep ...
  • 深度解析GaussDB(DWS)+Flink如何增強湖倉增量數據在不同數據模型層之間的實時流動能力,如何為消息數據流提供高性能通用入庫能力,又如何構建極致的端到端實時數倉解決方案。 ...
  • 提要(廢話): 最近我將筆記本重裝了,為了保留之前的程式,我把相關的註冊表和環境備份了下來,重裝之後重新導入成功再現了部分軟體。如MySQL這樣的程式,都是預設安裝在C盤之中的,雖然C盤的程式文件我也做了備份並且重新拷貝到了新系統C盤裡,但MySQL無法啟動了,同時我更新了系統之後就把安裝源MSI文 ...
  • 在實際項目中,從Kafka到HDFS的數據是每天自動生成一個文件,按日期區分。而且Kafka在不斷生產數據,因此看看kettle是不是需要時刻運行?能不能按照每日自動生成數據文件? 為了測試實際項目中的海豚定時調度從Kafka到HDFS的Kettle任務情況,特地提前跑一下海豚定時調度這個任務,看看 ...
  • 在當前快速發展的技術格局中,企業尋求創新解決方案來簡化運營並提高效率成為一種趨勢。 Apache DolphinScheduler作為一個強大的工具,允許跨分散式系統進行複雜的工作流任務調度。本文將深入探討如何將Apache DolphinScheduler適配並整合進現代IT環境,提升其在雲原生部 ...
  • 本文分享自華為雲社區《DTC2024,華為雲資料庫創新融合大發展,打造世界級資料庫!》,作者:GaussDB 資料庫。 4月12日-13日,以“智能·雲原生·一體化——DB與Al協同創新,模型與架構融合發展”為主題的第十三屆數據技術嘉年華(DTC 2024)在北京新雲南皇冠假日酒店成功舉行。作為本次 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...