天天看點

存儲優化(2)-排序引起的慢查詢優化

摘要

排序引起的慢查詢,通常不是那麼容易發現,經常和資料分布有關系。往往在業務剛開始時并沒有什麼問題,但是随着業務的發展,資料分布呈現一種特定的規律,導緻了慢查詢,或者并不是什麼慢查詢,但是随着并發請求數增加,資料庫的IOPS使用率變高,進一步導緻cpu/記憶體使用率飙高。造成線上故障。

問題

因為排序引起的問題遇到很多次

例1:某日收到線上cpu告警

然後檢視慢sql日志

大量的慢查詢指向了這個查詢

SELECT
        id,
        prize_id,
        user_id,
        name,
		biz_id
        FROM play 
        WHERE biz_id = xx
        AND status = 1
        AND prize_type = '大獎'
        ORDER BY id DESC
        LIMIT 0, 10
           

play是抽獎記錄表,sql是查抽中獎品的前10個大獎中獎者,來吸引其他使用者參與抽獎,biz_id建了索引

例2 某日上線一個新功能,在第五次壓測時,資料庫cpu告警

檢視資料庫慢日志,沒有一條慢sql(耗時>100ms)。最後通過查閱代碼,sql調用統計。發現有大量下面的SQL調用

SELECT
        id,
        commit_id
        FROM commit_record
        WHERE biz_id = 'xxx' 
        AND id >=  #{fromId}
       AND id <= #{toId}
           

biz_id有索引

例3 某日線上服務報API響應時間超過X秒

通過檢視應用日志,發現大量

com.mongodb.MongoSocketReadTimeoutException:

mongo的錯誤。經過多重定位,發現從庫的IOPS使用率快接近100%了,同時發現有些慢查詢

"query":{"find":"historyRecord","filter":{"bizId":1234567,"version":23},"sort":{"_id":-1},"limit":1}}
           

索引是bizId,version的聯合索引

問題分析

這幾個查詢造成的線上問題的形式雖然各有不同,但本質上都是一樣,無法利用索引排序,需要用到資料庫排序,當記憶體夠大或沒超過排序上限時,就會在記憶體中排序,這樣單個查詢相對比較快,但是并發量高了,記憶體容量不夠了,需要進行磁盤排序時,就會變得很慢。

然後經過仔細觀察,發現容易寫出這種語句,忽視了排序造成的風險。常常是根據主鍵排序。開發者容易想當然的以為主鍵是有索引的,是以排序會走索引,是以不會有什麼大問題。但其實像例子中那些案例,都是無法利用索引排序的。

曾經在mongo索引篇介紹聯合索引如何建立時也提到過。

總結一下,造成資料庫服務問題主要根由是

  1. 查詢沒有利用到索引排序
  2. 索引過濾後下面資料仍然有很多,需要掃描排序的資料很多
  3. 請求的并發量很高,資料庫IOPS使用率高,記憶體占用高。

問題解決

首先,日常開發時避免寫出這種SQL,尤其針對資料量比較大的表。或者索引下資料分布可能不均勻的情況。

線上解決

收到線上警告,發現是此類問題。

  1. 判斷業務側能否降級,即減少此類查詢。確定不要影響其他業務。
  2. 資料庫更新配置(需要做到對業務無影響)

線上問題的臨時解決方案隻能解一時燃煤之急,真正的解決問題還是需要從查詢着手。

查詢優化

  1. 業務側避免此類查詢

    從業務側分析,是不是需要此類查詢。比如例3,bizId,version_id是不是本身可以作為有序的,版本号version_id可以設計成有序的,這樣就不需要根據主鍵_id來保持有序

  2. 減少并發

    是不是所有的這類查詢都是必須的,能不能接受緩存。

  3. 引入其他存儲方案

    比如例1,業務需要查詢按照時間順序的中大獎的前N個人。這個業務側可以将資料儲存到在redis中,listz中存topN的資料。然後發現有中大獎的人,扔到redis隊列即可。

  4. 增加一個聯合索引

    比如例3可以增加一個bizId,version,_id聯合索引

    "query":{"find":"historyRecord","filter":{"bizId":1234567,"version":23},"sort":{"_id":-1},"limit":1}}