前言最近我在公司優(yōu)化過幾個(gè)慢查詢接口的性能,總結(jié)了一些心得體會(huì)拿出來跟大家一起分享一下,,希望對(duì)你會(huì)有所幫助,。 我們使用的數(shù)據(jù)庫是 通常情況下,分頁接口一般會(huì)查詢兩次數(shù)據(jù)庫,,第一次是獲取具體數(shù)據(jù),,第二次是獲取總的記錄行數(shù),然后把結(jié)果整合之后,,再返回,。 查詢具體數(shù)據(jù)的sql,比如是這樣的:`
它沒有性能問題,。 但另外一條使用count(*)查詢總記錄行數(shù)的sql,,例如:
卻存在性能差的問題。 為什么會(huì)出現(xiàn)這種情況呢,? 1 count(*)為什么性能差,?在Mysql中, 而 在Mysql中使用最多的存儲(chǔ)引擎是: 在myisam中會(huì)把總行數(shù)保存到磁盤上,使用count(*)時(shí),,只需要返回那個(gè)數(shù)據(jù)即可,,無需額外的計(jì)算,所以執(zhí)行效率很高,。 而innodb則不同,,由于它支持事務(wù),有 在innodb使用count(*)時(shí),,需要從存儲(chǔ)引擎中一行行的讀出數(shù)據(jù),然后累加起來,,所以執(zhí)行效率很低,。 如果表中數(shù)據(jù)量小還好,一旦表中數(shù)據(jù)量很大,,innodb存儲(chǔ)引擎使用count(*)統(tǒng)計(jì)數(shù)據(jù)時(shí),,性能就會(huì)很差,。 2 如何優(yōu)化count(*)性能?從上面得知,,既然 我們可以從以下幾個(gè)方面著手,。 2.1 增加redis緩存對(duì)于簡單的count(*),,比如:統(tǒng)計(jì)瀏覽總次數(shù)或者瀏覽總?cè)藬?shù),我們可以直接將接口使用redis緩存起來,,沒必要實(shí)時(shí)統(tǒng)計(jì),。 當(dāng)用戶打開指定頁面時(shí),在緩存中每次都設(shè)置成count = count+1即可,。 用戶第一次訪問頁面時(shí),,redis中的count值設(shè)置成1。用戶以后每訪問一次頁面,,都讓count加1,,最后重新設(shè)置到redis中。 image
這樣在需要展示數(shù)量的地方,,從redis中查出count值返回即可,。 該場景無需從數(shù)據(jù)埋點(diǎn)表中使用count(*)實(shí)時(shí)統(tǒng)計(jì)數(shù)據(jù),性能將會(huì)得到極大的提升,。 不過在高并發(fā)的情況下,可能會(huì)存在緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致的問題,。 但對(duì)于統(tǒng)計(jì)瀏覽總次數(shù)或者瀏覽總?cè)藬?shù)這種業(yè)務(wù)場景,,對(duì)數(shù)據(jù)的準(zhǔn)確性要求并不高,容忍數(shù)據(jù)不一致的情況存在,。 2.2 加二級(jí)緩存對(duì)于有些業(yè)務(wù)場景,,新增數(shù)據(jù)很少,大部分是統(tǒng)計(jì)數(shù)量操作,,而且查詢條件很多,。這時(shí)候使用傳統(tǒng)的count(*)實(shí)時(shí)統(tǒng)計(jì)數(shù)據(jù),性能肯定不會(huì)好,。 假如在頁面中可以通過id,、name、狀態(tài),、時(shí)間,、來源等,一個(gè)或多個(gè)條件,,統(tǒng)計(jì)品牌數(shù)量,。 這種情況下用戶的組合條件比較多,,增加聯(lián)合索引也沒用,用戶可以選擇其中一個(gè)或者多個(gè)查詢條件,,有時(shí)候聯(lián)合索引也會(huì)失效,,只能盡量滿足用戶使用頻率最高的條件增加索引。 也就是有些組合條件可以走索引,,有些組合條件沒法走索引,,這些沒法走索引的場景,該如何優(yōu)化呢,? 答:使用 二級(jí)緩存其實(shí)就是內(nèi)存緩存。 我們可以使用 目前 只需在需要增加二級(jí)緩存的查詢方法中,,使用
然后自定義cacheKeyGenerator,用于指定緩存的key,。
這個(gè)key是由各個(gè)條件組合而成,。 這樣通過某個(gè)條件組合查詢出品牌的數(shù)據(jù)之后,會(huì)把結(jié)果緩存到內(nèi)存中,,設(shè)置過期時(shí)間為5分鐘,。 后面用戶在5分鐘內(nèi),使用相同的條件,,重新查詢數(shù)據(jù)時(shí),,可以直接從二級(jí)緩存中查出數(shù)據(jù),直接返回了,。 這樣能夠極大的提示count(*)的查詢效率,。 但是如果使用二級(jí)緩存,可能存在不同的服務(wù)器上,,數(shù)據(jù)不一樣的情況,。我們需要根據(jù)實(shí)際業(yè)務(wù)場景來選擇,沒法適用于所有業(yè)務(wù)場景,。 2.3 多線程執(zhí)行不知道你有沒有做過這樣的需求:統(tǒng)計(jì)有效訂單有多少,,無效訂單有多少。 這種情況一般需要寫兩條sql,,統(tǒng)計(jì)有效訂單的sql如下:
統(tǒng)計(jì)無效訂單的sql如下:
但如果在一個(gè)接口中,,同步執(zhí)行這兩條sql效率會(huì)非常低。 這時(shí)候,可以改成成一條sql:
使用 但有個(gè)問題:status字段只有1和0兩個(gè)值,,重復(fù)度很高,區(qū)分度非常低,,不能走索引,,會(huì)全表掃描,效率也不高,。 還有其他的解決方案不,? 答:使用多線程處理,。 我們可以使用 2.4 減少join的表大部分的情況下,,使用count(*)是為了實(shí)時(shí)統(tǒng)計(jì)總數(shù)量的,。 但如果表本身的數(shù)據(jù)量不多,,但join的表太多,也可能會(huì)影響count(*)的效率,。 比如在查詢商品信息時(shí),,需要根據(jù)商品名稱、單位,、品牌,、分類等信息查詢數(shù)據(jù)。 這時(shí)候?qū)懸粭lsql可以查出想要的數(shù)據(jù),,比如下面這樣的:
使用product表去 其實(shí)這些查詢條件,,在product表中都能查詢出數(shù)據(jù),,沒必要join額外的表,。 我們可以把sql改成這樣:
在count(*)時(shí)只查product單表即可,,去掉多余的表join,讓查詢效率可以提升不少,。 2.5 改成ClickHouse有些時(shí)候,,join的表實(shí)在太多,沒法去掉多余的join,,該怎么辦呢,? 比如上面的例子中,查詢商品信息時(shí),需要根據(jù)商品名稱,、單位名稱,、品牌名稱、分類名稱等信息查詢數(shù)據(jù),。 這時(shí)候根據(jù)product單表是沒法查詢出數(shù)據(jù)的,,必須要去 答:可以將數(shù)據(jù)保存到 ClickHouse是基于 為了避免對(duì)業(yè)務(wù)代碼的嵌入性,可以使用 查詢數(shù)據(jù)時(shí),,從ClickHouse當(dāng)中查詢,,這樣使用count(*)的查詢效率能夠提升N倍。
其實(shí)如果查詢條件非常多,,使用ClickHouse也不是特別合適,,這時(shí)候可以改成 3 count的各種用法性能對(duì)比既然說到count(*),就不能不說一下count家族的其他成員,,比如:count(1),、count(id)、count(普通索引列),、count(未加索引列),。 那么它們有什么區(qū)別呢?
由此,,最后count的性能從高到低是:
所以,,其實(shí) 意不意外,,驚不驚喜,? 千萬別跟 |
|