国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

記一次 MySQL 的慢查優(yōu)化

FuisonDesign / 3328人閱讀

摘要:最近遇見一個的慢查問題,于是排查了下,這里把相關(guān)的過程做個總結(jié)。而且,我檢查了子查詢的表的索引,中用到的查詢條件都已經(jīng)增加了索引。還好我們的子查詢加了必要的索引,不然結(jié)果會更加慘不忍睹。這個結(jié)果真是太坑爹,而且十分違反直覺。

最近遇見一個 MySQL 的慢查問題,于是排查了下,這里把相關(guān)的過程做個總結(jié)。

定位原因

我首先查看了 MySQL 的慢查詢?nèi)罩荆l(fā)現(xiàn)有這樣一條 query 耗時非常長(大概在 1 秒多),而且掃描的行數(shù)很大(10 多萬條數(shù)據(jù),差不多是全表了):

SELECT * FROM tgdemand_demand t1
WHERE
  (
    t1.id IN
    (
      SELECT t2.demand_id
      FROM tgdemand_job t2
      WHERE (t2.state = "working" AND t2.wangwang = "abc")
    )
    AND
    NOT (t1.state = "needConfirm")
  )
ORDER BY t1.create_date DESC

這個查詢不是很復(fù)雜,首先執(zhí)行一個子查詢,取到任務(wù)的狀態(tài)(state)是 "working" 并且任務(wù)的關(guān)聯(lián)人 (wangwang)是"abc"的所有需求 id(這個設(shè)計師進(jìn)行中的任務(wù)對應(yīng)的需求 id),然后再到主表 tgdemand_demand 中帶入剛才的 id 集合,查詢出需求狀態(tài)(state)不是 "needConfirm" 的所有需求,最后進(jìn)行一個排序。

按道理子查詢篩選出 id 后到主表過濾是直接使用到主鍵,應(yīng)該是很快的啊。而且,我檢查了子查詢的 tgdemand_job 表的索引,where 中用到的查詢條件都已經(jīng)增加了索引。怎么會這樣呢?

于是,我對這個 query 執(zhí)行了一個 explain(輸出 sql 語句的執(zhí)行計劃),看看 MySQL 的執(zhí)行計劃是怎樣的。輸出如下:

我們看到,第一行是 t1 表,type 是 ALL(全表掃描),rows(影響行數(shù))是 157089,沒有用到任何索引;第二行是 t2 表,用到了索引。和我之前理解的執(zhí)行順序完全不一樣!

為什么 MySQL 不是先執(zhí)行子查詢,而是對 t1 表進(jìn)行了全表掃描呢?我們仔細(xì)看第二行的 select_type,發(fā)現(xiàn)它的值是 DEPENDENT_SUBQUERY,意思是這個子查詢的查詢方式依賴外層的查詢。這是什么意思?

實(shí)際上,MySQL 對于這種子查詢會進(jìn)行改寫,上面的 SQL 會被改寫成下面的形式:

SELECT * FROM tgdemand_demand t1 WHERE EXISTS (
  SELECT * FROM tgdemand_job t2 WHERE t1.id = t2.demand_id AND (t2.state = "working" AND t2.wangwang = "abc")
) AND NOT (t1.state = "needConfirm")
ORDER BY t1.create_date DESC;

這表示,SQL 會去掃描 tgdemand_demand 表的所有數(shù)據(jù),每條數(shù)據(jù)再傳入到子查詢中與表 tgdemand_job 進(jìn)行關(guān)聯(lián),執(zhí)行子查詢,子查詢根本不會先執(zhí)行,而且子查詢會執(zhí)行 157089 次(外層表的記錄數(shù)量)。還好我們的子查詢加了必要的索引,不然結(jié)果會更加慘不忍睹。

這個結(jié)果真是太坑爹,而且十分違反直覺。對于慢查詢,千萬不要想當(dāng)然,還是多多 explain,看看數(shù)據(jù)庫實(shí)際上是怎么去執(zhí)行的。

問題修復(fù)

既然子查詢會被改寫,那最簡單的解決方案就是不用子查詢,將內(nèi)層獲取需求 id 的 SQL 多帶帶拿出來執(zhí)行,取到結(jié)果后再執(zhí)行一條 SQL 去獲取實(shí)際的數(shù)據(jù)。大概像這樣(下面的語句是不合法的,只是示意):

ids = SELECT t2.demand_id
FROM tgdemand_job t2
WHERE (t2.state = "working" AND t2.wangwang = "abc");

SELECT * FROM tgdemand_demand t1
WHERE
  (
    t1.id IN ids
    AND
    NOT (t1.state = "needConfirm")
  )
ORDER BY t1.create_date DESC;

說干咱就干,我找到了下面的代碼(是 python 語言寫的):

demand_ids = Job.objects.filter(wangwang=user["wangwang"], state="working").values_list("demand_id", flat=True)

demands = Demand.objects.filter(id__in=demand_ids).exclude(state__in=["needConfirm"]).order_by("-create_date")

咦!這不是和我想得是一樣的嘛?先查出需求 id(代碼第一行),然后用 id 集合再去執(zhí)行實(shí)際的查詢(代碼第二行)。為什么經(jīng)過 ORM 框架的處理后產(chǎn)出的 SQL 就不一樣了呢?

帶著這個問題我搜索了一番。原來 Django 自帶的 ORM 框架生成的 QuerySet 是懶執(zhí)行的(lazy evaluated),我們可以將這種 QuerySet 到處傳,直到需要時才會實(shí)際的執(zhí)行 SQL。

比如,我們代碼里面的 Job.objects.filter(wangwang=user["wangwang"], state="working").values_list("demand_id", flat=True) 這個 QuerySet 實(shí)際上并沒有執(zhí)行,就被作為參數(shù)傳遞給了 id__in,當(dāng) Demand.objects.filter(id__in=demand_ids).exclude(state__in=["needConfirm"]).order_by("-create_date") 這個 QuerySet 執(zhí)行時,剛才未執(zhí)行的 QuerySet 才開始作為 SQL 執(zhí)行,于是生成了最開始的 SQL 語句。

既然如此,我們的目的要讓 QuerySet 提前執(zhí)行,獲得結(jié)果集。根據(jù)文檔,對 QuerySet 進(jìn)行循環(huán)、slice、取 len、list 轉(zhuǎn)換的時候被執(zhí)行。于是我將代碼更改為了下面的樣子:

demand_ids = list(Job.objects.filter(wangwang=user["wangwang"], state="working").values_list("demand_id", flat=True))

demands = Demand.objects.filter(id__in=demand_ids).exclude(state__in=["needConfirm"]).order_by("-create_date")

終于,頁面打開速度恢復(fù)正常了。

實(shí)際上,我們也可以對 SQL 進(jìn)行改寫來解決問題:

select * from tgdemand_demand t1, (select t.demand_id from tgdemand_job t where t.state = "working" and t.wangwang = "abc") t2
where t1.id=t2.demand_id and not (t1.state = "needConfirm")
order by t1.create_date DESC

思路是去掉子查詢,換用 2 個表進(jìn)行 join 的方式來取得數(shù)據(jù)。這里就不展開了。

感想

框架可以提高生產(chǎn)率的前提是對背后的原理足夠了解,不然應(yīng)用很可能就會在某個時間暴露出一些隱蔽的要命問題(這些問題在小規(guī)模階段可能根本都發(fā)現(xiàn)不了......)。保證應(yīng)用的健壯真是個大學(xué)問,還有很多東西值得我們?nèi)ヌ剿鳌?/p> 參考資料

http://www.cnblogs.com/zhengyun_ustc/p/slowquery3.html

http://dev.mysql.com/doc/refman/5.5/en/explain-output.html

https://docs.djangoproject.com/en/1.9/ref/models/querysets/

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/37701.html

相關(guān)文章

  • 深入解析 MongoDB Plan Cache

    摘要:所以在掃描次后,率先到達(dá)狀態(tài),那么此刻將停止掃描,進(jìn)入到算分的階段。除了這條引發(fā)故障的之外,其他的字段命中索引數(shù)量都非常小,有的甚至只有一條。那這里很明顯在中只去根據(jù)中執(zhí)行計劃的相關(guān)索引來進(jìn)行判斷是不合理的。 前段時間筆者遇到一個MongoBD Plan Cache的bug,于是深究了下MongoDB優(yōu)化器相關(guān)源碼。在這里分享給大家,一方面讓大家知道MongoDB優(yōu)化器工作原理,一方面...

    Pluser 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<