日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:胡呈清

愛可生 DBA 團(tuán)隊成員,擅長故障分析、性能優(yōu)化,個人博客:
https://www.jianshu.com/u/a95ec11f67a8,歡迎討論。

本文來源:原創(chuàng)投稿

*愛可生開源社區(qū)出品,原創(chuàng)內(nèi)容未經(jīng)授權(quán)不得隨意使用,轉(zhuǎn)載請聯(lián)系小編并注明來源。


有這么一個 SQL,外查詢 where 子句的 bizCustomerIncoming_id 字段,和子查詢 where 字句的 cid 字段都有高效索引,為什么這個 SQL 執(zhí)行的非常慢,需要全表掃描?

delete FROM biz_customer_incoming_path WHERE bizCustomerIncoming_id IN 
(SELECT id FROM biz_customer_incoming WHERE cid='315upfdv34umngfrxxxxxx');

我們從這么一個問題來引入接下來的內(nèi)容,如果你知道答案就不用繼續(xù)看下去了。

 

查詢優(yōu)化策略

對于不同類型的子查詢,優(yōu)化器會選擇不同的策略。

1. 對于 IN、=ANY 子查詢,優(yōu)化器有如下策略選擇:

  • semijoin
  • Materialization
  • exists

2. 對于 NOT IN、<>ALL 子查詢,優(yōu)化器有如下策略選擇:

  • Materialization
  • exists

3. 對于 derived 派生表,優(yōu)化器有如下策略選擇:

  • derived_merge,將派生表合并到外部查詢中(5.7 引入 );
  • 將派生表物化為內(nèi)部臨時表,再用于外部查詢。

注意:update 和 delete 語句中查詢不能使用 semijoin、materialization 優(yōu)化策略

 

優(yōu)化思路

那么這些策略分別是什么意思?為什么會有這些優(yōu)化策略?

為方便分析,先建兩張表:

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

drop procedure idata;
delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=1000)do
    insert into t2 values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
call idata();

create table t1 like t2;
insert into t1 (select * from t2 where id<=100)

有以下子查詢示例:

SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2 WHERE id < 10);

你肯定認(rèn)為這個 SQL 會這樣執(zhí)行:

SELECT t2.b FROM t2 WHERE id < 10; 
結(jié)果:1,2,3,4,5,6,7,8,9 
select * from t1 where t1.a in(1,2,3,4,5,6,7,8,9);

但實際上 MySQL 并不是這樣做的。MySQL 會將相關(guān)的外層表壓到子查詢中,優(yōu)化器認(rèn)為這樣效率更高。也就是說,優(yōu)化器會將上面的 SQL 改寫成這樣:

select * from t1 where exists(select b from t2 where id < 10 and t1.a=t2.b);

執(zhí)行計劃為:

+----+--------------------+-------+-------+---------+------+----------+-------------+
| id | select_type        | table | type  | key     | rows | filtered | Extra       |
+----+--------------------+-------+-------+---------+------+----------+-------------+
|  1 | PRIMARY            | t1    | ALL   | NULL    |  100 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | t2    | range | PRIMARY |    9 |    10.00 | Using where |
+----+--------------------+-------+-------+---------+------+----------+-------------+

不相關(guān)子查詢變成了關(guān)聯(lián)子查詢(select_type:DEPENDENT SUBQUERY),子查詢需要根據(jù) b 來關(guān)聯(lián)外表 t1,因為需要外表的 t1 字段,所以子查詢是沒法先執(zhí)行的。執(zhí)行流程為:

1. 掃描 t1,從 t1 取出一行數(shù)據(jù) R;

2. 從數(shù)據(jù)行 R 中,取出字段 a 執(zhí)行子查詢,如果得到結(jié)果為 TRUE,則把這行數(shù)據(jù) R 放到結(jié)果集;

3. 重復(fù) 1、2 直到結(jié)束。

總的掃描行數(shù)為 100+100*9=1000(這是理論值,實際值為 964,怎么來的一直沒想明白,看規(guī)律是子查詢結(jié)果集每多一行,總掃描行數(shù)就會少幾行)。

Semi-join

這樣會有個問題,如果外層表是一個非常大的表,對于外層查詢的每一行,子查詢都得執(zhí)行一次,這個查詢的性能會非常差。我們很容易想到將其改寫成 join 來提升效率:

select t1.* from t1 join t2 on t1.a=t2.b and t2.id<10;

這樣優(yōu)化可以讓 t2 表做驅(qū)動表,t1 表關(guān)聯(lián)字段有索引,查找效率非常高。

但這里會有個問題,join 是有可能得到重復(fù)結(jié)果的,而 in(select ...) 子查詢語義則不會得到重復(fù)值。而 semijoin 正是解決重復(fù)值問題的一種特殊聯(lián)接。在子查詢中,優(yōu)化器可以識別出 in 子句中每組只需要返回一個值,在這種情況下,可以使用 semijoin 來優(yōu)化子查詢,提升查詢效率。這是 MySQL 5.6 加入的新特性,MySQL 5.6 以前優(yōu)化器只有 exists 一種策略來“優(yōu)化”子查詢。經(jīng)過 semijoin 優(yōu)化后的 SQL 和執(zhí)行計劃分為:

select 
    `t1`.`id`,`t1`.`a`,`t1`.`b` 
from `t1` semi join `t2` 
where
    ((`t1`.`a` = `<subquery2>`.`b`) 
    and (`t2`.`id` < 10)); 
##注意這是優(yōu)化器改寫的SQL,客戶端上是不能用 semi join 語法的 
+----+--------------+-------------+-------+---------+---------------+------+-------------+
| id | select_type  | table       | type  | key     | ref           | rows | Extra       |
+----+--------------+-------------+-------+---------+---------------+------+-------------+
|  1 | SIMPLE       | <subquery2> | ALL   | NULL    | NULL          | NULL | Using where |
|  1 | SIMPLE       | t1          | ref   | a       | <subquery2>.b |    1 | NULL        |
|  2 | MATERIALIZED | t2          | range | PRIMARY | NULL          |    9 | Using where |
+----+--------------+-------------+-------+---------+---------------+------+-------------+

semijoin 優(yōu)化實現(xiàn)比較復(fù)雜,其中又分 FirstMatch、Materialize 等策略,上面的執(zhí)行計劃中 select_type=MATERIALIZED 就是代表使用了 Materialize 策略來實現(xiàn)的 semijoin,后面有專門的文章介紹 semijoin,這里不展開。這里 semijoin 優(yōu)化后的執(zhí)行流程為:

1. 先執(zhí)行子查詢,把結(jié)果保存到一個臨時表中,這個臨時表有個主鍵用來去重;

2. 從臨時表中取出一行數(shù)據(jù) R;

3. 從數(shù)據(jù)行 R 中,取出字段 b 到被驅(qū)動表 t1 中去查找,滿足條件則放到結(jié)果集;

4. 重復(fù)執(zhí)行 2、3,直到結(jié)束。

這樣一來,子查詢結(jié)果有 9 行,即臨時表也有 9 行(這里沒有重復(fù)值),總的掃描行數(shù)為 9+9+9*1=27 行,比原來的 1000 行少了很多。

Materialization

MySQL 5.6 版本中加入的另一種優(yōu)化特性 materialization,就是把子查詢結(jié)果物化成臨時表,然后代入到外查詢中進(jìn)行查找,來加快查詢的執(zhí)行速度。內(nèi)存臨時表包含主鍵(hash 索引),消除重復(fù)行,使表更小。如果子查詢結(jié)果太大,超過 tmp_table_size 大小,會退化成磁盤臨時表。這跟前面提到的“我們誤以為的”過程相似,這樣子查詢只需要執(zhí)行一次,而不是對于外層查詢的每一行都得執(zhí)行一遍。不過要注意的是,這樣外查詢依舊無法通過索引快速查找到符合條件的數(shù)據(jù),只能通過全表掃描或者全索引掃描,materialization 優(yōu)化后的執(zhí)行計劃為:

+----+-------------+-------+-------+---------+------+------+-------------+
| id | select_type | table | type  | key     | ref  | rows | Extra       |
+----+-------------+-------+-------+---------+------+------+-------------+
|  1 | PRIMARY     | t1    | ALL   | NULL    | NULL |  100 | Using where |
|  2 | SUBQUERY    | t2    | range | PRIMARY | NULL |    9 | Using where |
+----+-------------+-------+-------+---------+------+------+-------------+

總掃描行數(shù)為 100+9=109。

semijoin 和 materialization 的開啟是通過 optimizer_switch 參數(shù)中的 semijoin={on|off}、materialization={on|off} 標(biāo)志來控制的。上文中不同的執(zhí)行計劃就是對 semijoin 和 materialization 進(jìn)行開/關(guān)產(chǎn)生的。特意考古找了下 MySQL 5.5 的官方手冊,優(yōu)化策略相當(dāng)稀少:

技術(shù)分享 | MySQL 子查詢優(yōu)化

 

總的來說對于子查詢,先檢查是否滿足各種優(yōu)化策略的條件(比如子查詢中有 union 則無法使用 semijoin 優(yōu)化),然后優(yōu)化器會按成本進(jìn)行選擇,實在沒得選就會用 exists 策略來“優(yōu)化”子查詢,exists 策略是沒有參數(shù)來開啟或者關(guān)閉的。

 

小結(jié)

回到開篇的問題,答案是:delete 無法使用 semijoin、materialization 優(yōu)化策略,會以 exists 方式執(zhí)行,外查詢即 delete
biz_customer_incoming_path 表時必須要進(jìn)行全表掃描。優(yōu)化的方法也很簡單,改成 join 即可(這里是 delete,不用擔(dān)心重復(fù)行問題):

delete 
    biz_customer_incoming_path 
FROM biz_customer_incoming_path a  join biz_customer_incoming b 
WHERE 
    a.bizCustomerIncoming_id=b.id 
    and b.cid='7Ex46Dz22Fqq6iuPCLPlzQ';

參考資料

1. https://dev.mysql.com/doc/refman/5.7/en/subquery-optimization.html

2. 《高性能 MySQL》第 6.5.1 章節(jié)

分享到:
標(biāo)簽:查詢 MySQL
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定