天天看點

如何從設計和規範上規避RDS性能問題?【阿裡雲MVP月度分享】

在初創型網際網路公司中,開發們整天想的唯一一件事,就是“把功能做出來”。而當公司業務量逐漸上漲、甚至翻了好幾番之後,最開始的程式上的問題,就一個接一個地暴露了出來。

其中,最明顯的,就是資料庫的壓力問題。下文提到的資料庫,都指RDS for MYSQL。

場景一:寬表

現象

開發在設計表結構的時候,很大程度上是在參考産品原型的設計。通常會把産品原型中,需要一起查詢的條件、一起展示的字段,都放在同一張表中。而網際網路公司的産品疊代又是非常快的,新功能層出不窮,開發沒有時間重新梳理資料庫結構,就盲目地在原來的表上增加備援字段,為了使功能盡快開發完成。這就使得某些“老古董”的表,越來越寬,七八十個字段以上的大寬表越來越多。

建議

(1)單個InnoDB表的字段數,建議少于50個;

(2)大字段,例如:text、blob類型,考慮單獨存放;

場景二:清單的行數查詢

在背景或者部分前端的功能中,常常會出現類似MIS系統的清單查詢功能。一般這種功能,都是按照標明的查詢條件,先查出行數,再按照分頁規則查詢出第一頁的資料。于是,就出現了這樣的幾種情況:

(1)查詢清單明細,是需要使用多表關聯查詢的。開發為了友善,直接把這個查詢明細的“多表關聯”的SQL拿過來,把

select

後邊的字段改成

count(*)

,就直接作為查詢行數的SQL,甚至連

order by

都不去掉。而從業務上來說,查詢行數的時候,隻需要查詢其中一張表就可以了;

(2)代碼邏輯的設計不合理,導緻某些開發,直接套用“首頁”邏輯,查詢後續每一頁的時候,都重新查詢了一遍行數;

(1)增加SQL稽核機制,不合規範的SQL不允許上線;

(2)增加代碼稽核機制;

場景三:查詢不走索引

很常見的情況是,某些表最開始的資料量很小,後來由于産品功能重心的調整,變成了大表。之前做好的程式、SQL、表結構卻沒有跟着調整。就出現了很多大表查詢,沒走索引,導緻的慢查詢。而慢查詢堆積多了,整個資料庫就癱瘓了,于是就出現了“次要業務拖累主要業務”的現象。

(1)根據查詢場景,設定合理的索引,組合索引優先;

(2)當組合索引和單字段索引同時存在時,建議删掉單字段索引,避免優化器做“無用功”;

場景四:線上資料和線下資料沒有隔離

這裡說的線上資料,指的是直接面對廣大使用者的資料;線下資料,是面向公司内部客服、營運的背景系統用到的資料。背景系統由于工作職責的不同,會有各種各樣的查詢需求,有的可能會很大、很複雜,比如導出一整個月、一整個季度的資料。會直接導緻資料的壓力非常大,進而影響了整個資料庫執行個體,導緻線上系統發生故障。

  • 線上資料的特點是:
    1. 通路量大;
    2. 每個使用者隻查自己的資料,可以命中索引;
    3. 查詢條件簡單;
    4. 傳回條數少;
    5. 對響應時間要求極高;
    6. 對資料的時效性要求高;
  • 線下資料的特點是:
    1. 通路量小;
    2. 背景同僚會查詢整個平台的資料,不容易命中索引;
    3. 查詢條件複雜;
    4. 傳回條數大;
    5. 對響應時間要求不高;
    6. 對資料的時效性要求不是特别高;

綜上,兩類資料從各個方面都是完全不同的。要把線上資料和線下資料隔離開來。更新時,統一更新線上資料;查詢時,線上查線上,線下查線下。線上資料通過DTS等實時資料同步的方式更新到線下。

場景五:過于依賴MYSQL,沒有考慮其他的存儲

某些清單查詢類場景,可能涉及到10~20個查詢條件,而且檢索資料量一般也很大。此時再使用MYSQL就比較吃力了,索引幾乎無法覆寫。

除了關系型資料庫之外,我們有很多不同的資料存儲的選擇,比如:搜尋引擎類、NOSQL類、時序類、緩存類,等等。應當根據不同的查詢場景,選擇最适合的資料存儲方式。企圖用MYSQL解決一切問題,是不明智的。

場景六:沒有站在資料庫的角度去思考

比如,子查詢。開發站在人類的角度思考問題,就會出現形如:

SELECT * FROM table1 WHERE id IN ( SELECT id FROM table2 );
           

這種子查詢。而MYSQL在處理子查詢的時候,是拿外層的每一條資料,去内層掃描,結果就是掃描了

table1的行數 × table2的行數

次。

避免使用子查詢,改為通過索引做表關聯等方式;

場景七:直接在資料庫中做計算

部分開發會在SQL中寫例如

case when

group by + count/sum

等的計算。MYSQL擅長的是,資料的查詢與存儲,并不擅長做計算——雖然它可以做。導緻出現了很多慢SQL。MYSQL隻對查詢做了優化,并沒有對計算做優化。

(1)

group by + count/sum

可以考慮進行預計算;

(2)

case when

可以在業務端或者前端進行;

(3)要有效利用每個工具最擅長做的事。

場景八:在索引字段上用函數

部分表在bigint類型的、存放時間戳的字段上做了索引,而查詢的條件是精确到天的。某些開發就會把SQL寫成:

WHERE from_unixtime(create_timestamp) >= '2018-01-01'
AND from_unixtime(create_timestamp) < '2018-02-01'
           

這樣。在索引字段上使用函數,索引就起不到作用,掃描資料的時候依然是全表掃描,并對每一行資料的create_timestamp做

from_unixtime

運算。

如:

WHERE from_unixtime(create_timestamp) >= '2018-01-01'
AND from_unixtime(create_timestamp) < '2018-02-01'
           

這種場景,可以改為:

WHERE create_timestamp >= unix_timestamp('2018-01-01')
AND create_timestamp < unix_timestamp('2018-02-01')
           

這樣,隻會計算一次,然後直接去比對索引。避免了全表掃描。

場景九:沒有充分利用緩存

部分對資料實時性要求不高的場景。會有相同條件的查詢頻繁執行的情況,甚至于并發執行多個相同查詢條件的查詢。這時候如果每次都查詢資料庫,勢必造成了資源的浪費。

把這部分查詢結果,緩存到redis中。把大部分請求量引到redis去。

場景十:單表資料量過大

由于對MYSQL依賴嚴重,導緻很多更适合存在NOSQL資料庫的資料,也被存到了MYSQL中,而且行數非常多。這樣的表,無論是查詢、還是更新、或是DDL操作,都需要停服之後、花大量時間去做。

(1)單表不要超過1千萬行,大小不要超過5G;如果超過,可以考慮分庫分表;

(2)根據場景,考慮用其他資料存儲工具、或其他業務上的邏輯來解決大表的問題;