1. 程式人生 > >資料庫優化方案整理

資料庫優化方案整理

一:優化說明

A:有資料表明,使用者可以承受的最大等待時間為8秒。資料庫優化策略有很多,設計初期,建立好的資料結構對於後期效能優化至關重要。因為資料庫結構是系統的基石,基礎打不好,使用各種優化策略,也不能達到很完美的效果。

B:資料庫優化的幾個方面
這裡寫圖片描述
​​
可以看出來,資料結構、SQL、索引是成本最低,且效果最好的優化手段。

C:效能優化是無止境的,當效能可以滿足需求時即可,不要過度優化。

二:優化方向

1. SQL以及索引的優化

首先要根據需求寫出結構良好的SQL,然後根據SQL在表中建立有效的索引。但是如果索引太多,不但會影響寫入的效率,對查詢也有一定的影響。

2. 合理的資料庫是設計

根據資料庫三正規化來進行表結構的設計。設計表結構時,就需要考慮如何設計才能更有效的查詢。

資料庫三正規化:
第一正規化:資料表中每個欄位都必須是不可拆分的最小單元,也就是確保每一列的原子性;
第二正規化:滿足一正規化後,表中每一列必須有唯一性,都必須依賴於主鍵;
第三正規化:滿足二正規化後,表中的每一列只與主鍵直接相關而不是間接相關(外來鍵也是直接相關),欄位沒有冗餘。

注意:沒有最好的設計,只有最合適的設計,所以不要過分注重理論。三正規化可以作為一個基本依據,不要生搬硬套。

有時候可以根據場景合理地反規範化:
A:分割表。
B:保留冗餘欄位。當兩個或多個表在查詢中經常需要連線時,可以在其中一個表上增加若干冗餘的欄位,以 避免表之間的連線過於頻繁,一般在冗餘列的資料不經常變動的情況下使用。
C:增加派生列。派生列是由表中的其它多個列的計算所得,增加派生列可以減少統計運算,在資料彙總時可以大大縮短運算時間。

資料庫五大約束:
A:PRIMARY key:設定主鍵約束;
B:UNIQUE:設定唯一性約束,不能有重複值;
C:DEFAULT 預設值約束
D:NOT NULL:設定非空約束,該欄位不能為空;
E:FOREIGN key :設定外來鍵約束。

欄位型別選擇:
A:儘量使用TINYINT、SMALLINT、MEDIUM_INT作為整數型別而非INT,如果非負則加上UNSIGNED
B:VARCHAR的長度只分配真正需要的空間
C:使用列舉或整數代替字串型別
D:儘量使用TIMESTAMP而非DATETIME
E:單表不要有太多欄位,建議在20以內
F:避免使用NULL欄位,很難查詢優化且佔用額外索引空間

3. 系統配置的優化

例如:MySQL資料庫my.cnf

4. 硬體優化

更快的IO、更多的記憶體。一般來說記憶體越大,對於資料庫的操作越好。但是CPU多就不一定了,因為他並不會用到太多的CPU數量,有很多的查詢都是單CPU。另外使用高的IO(SSD、RAID),但是IO並不能減少資料庫鎖的機制。所以說如果查詢緩慢是因為資料庫內部的一些鎖引起的,那麼硬體優化就沒有什麼意義。

三:優化方案

程式碼優化

之所以把程式碼放到第一位,是因為這一點最容易引起技術人員的忽視。很多技術人員拿到一個性能優化的需求以後,言必稱快取、非同步、JVM等。實際上,第一步就應該是分析相關的程式碼,找出相應的瓶頸,再來考慮具體的優化策略。有一些效能問題,完全是由於程式碼寫的不合理,通過直接修改一下程式碼就能解決問題的,比如for迴圈次數過多、作了很多無謂的條件判斷、相同邏輯重複多次等。

舉個栗子:
一個update操作,先查詢出entity,再執行update,這樣無疑多了一次資料庫互動。還有一個問題,update語句可能會操作一些無需更新的欄位。

我們可以將表單中涉及到的屬性,以及updateTime,updateUser等賦值到entity,直接通過pdateByPrimaryKeySelective,去update特定欄位。

​​

定位慢SQL,並優化

這是最常用、每一個技術人員都應該掌握基本的SQL調優手段(包括方法、工具、輔助系統等)。這裡以MySQL為例,最常見的方式是,由自帶的慢查詢日誌或者開源的慢查詢系統定位到具體的出問題的SQL,然後使用explain、profile等工具來逐步調優,最後經過測試達到效果後上線。

SqlServer執行計劃:

通過執行計劃,我們能得到哪些資訊:
A:哪些步驟花費的成本比較高
B:哪些步驟產生的資料量多,資料量的多少用線條的粗細表示,很直觀
C:每一步執行了什麼動作

具體優化手段:

A:儘量少用(或者不用)sqlserver 自帶的函式
select id from t where substring(name,1,3) = ’abc’
select id from t where datediff(day,createdate,’2005-11-30′) = 0
可以這樣查詢:
select id from t where name like ‘abc%’
select id from t where createdate >= ‘2005-11-30’ and createdate < ‘2005-12-1’

B:連續數值條件,用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
C:Update 語句,如果只更改1、2個欄位,不要Update全部欄位,否則頻繁呼叫會引起明顯的效能消耗
D:儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型
E:不建議使用 select * from t ,用具體的欄位列表代替“*”,不要返回用不到的任何欄位。儘量避免向客戶 端返回大資料量,若資料量過大,應該考慮相應需求是否合理
F:表與表之間通過一個冗餘欄位來關聯,要比直接使用JOIN有更好的效能
G:select count(*) from table;這樣不帶任何條件的count會引起全表掃描
連線池調優
我們的應用為了實現資料庫連線的高效獲取、對資料庫連線的限流等目的,通常會採用連線池類的方案,即每一個應用節點都管理了一個到各個資料庫的連線池。隨著業務訪問量或者資料量的增長,原有的連線池引數可能不能很好地滿足需求,這個時候就需要結合當前使用連線池的原理、具體的連線池監控資料和當前的業務量作一個綜合的判斷,通過反覆的幾次除錯得到最終的調優引數。

​​

合理使用索引

索引一般情況下都是高效的。但是由於索引是以空間換時間的一種策略,索引本身在提高查詢效率的同時會影響插入、更新、刪除的效率,頻繁寫的表不宜建索引。

選擇合適的索引列,選擇在where,group by,order by,on從句中出現的列作為索引項,對於離散度不大的列沒有必要建立索引。
主鍵已經是索引了,所以primay key 的主鍵不用再設定unique唯一索引

索引型別
主鍵索引 (PRIMARY KEY)
唯一索引 (UNIQUE)
普通索引 (INDEX)
組合索引 (INDEX)
全文索引 (FULLTEXT)

可以應用索引的操作符
大於等於
Between
IN
LIKE 不以 % 開頭

不能應用索引的操作符
NOT IN
LIKE %_ 開頭

如何選擇索引欄位
A:欄位出現在查詢條件中,並且查詢條件可以使用索引
B:通常對數字的索引和檢索要比對字串的索引和檢索效率更高
C:語句執行頻率高,一天會有幾千次以上
D:通過欄位條件可篩選的記錄集很小
​​

無效索引
A:儘量不要在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描
B:應儘量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描。
C:應儘量避免在 where 子句中使用 or 來連線條件,如果一個欄位有索引,一個欄位沒有索引,將導致引擎放棄使用索引而進行全表掃描
select id from t where num=10 or Name = ‘admin’
可以這樣查詢:
select id from t where num = 10
union
select id from t where Name = ‘admin’
union all 返回所有資料,不管是不是重複。 union會自動壓縮,去除重複資料。

D:不做列運算
where age + 1 = 10,任何對列的操作都將導致表掃描,它包括資料庫教程函式、計算表示式等
E:查詢like,如果是 ‘%aaa’ 不會使用到索引

分表

分表方式
水平分割(按行)、垂直分割(按列)

分表場景
A: 根據經驗,mysql表資料一般達到百萬級別,查詢效率就會很低。
B: 一張表的某些欄位值比較大並且很少使用。可以將這些欄位隔離成單獨一張表,通過外來鍵關聯,例如考試成績,我們通常關注分數,不關注考試詳情。

水平分表策略
按時間分表:當資料有很強的實效性,例如微博的資料,可以按月分割。
按區間分表:例如使用者表 1到一百萬用一張表,一百萬到兩百萬用一張表。
hash分表:通過一個原始目標id或者是名稱按照一定的hash演算法計算出資料儲存的表名。

讀寫分離

當一臺伺服器不能滿足需求時,採用讀寫分離【寫: update/delete/add】的方式進行叢集。
一臺資料庫支援最大連線數是有限的,如果使用者的併發訪問很多,一臺伺服器無法滿足需求,可以叢集處理。mysql叢集處理技術最常用的就是讀寫分離。

主從同步:資料庫最終會把資料持久化到磁碟,叢集必須確保每個資料庫伺服器的資料是一致的。從庫讀主庫寫,從庫從主庫上同步資料。
讀寫分離:使用負載均衡實現,寫操作都往主庫上寫,讀操作往從伺服器上讀。

快取

快取分類
本地快取:HashMap/ConcurrentHashMap、Ehcache、Guava Cache等
快取服務:Redis/Tair/Memcache等

使用場景
短時間內相同資料重複查詢多次且資料更新不頻繁,這個時候可以選擇先從快取查詢,查詢不到再從資料庫載入並回設到快取的方式。此種場景較適合用單機快取。
高併發查詢熱點資料,後端資料庫不堪重負,可以用快取來扛。

快取作用
減輕資料庫的壓力,減少訪問時間。

快取選擇
如果資料量小,並且不會頻繁地增長又清空(這會導致頻繁地垃圾回收),那麼可以選擇本地快取。具體的話,如果需要一些策略的支援(比如快取滿的逐出策略),可以考慮Ehcache;如不需要,可以考慮HashMap;如需要考慮多執行緒併發的場景,可以考慮ConcurentHashMap。
其他情況,可以考慮快取服務。目前從資源的投入度、可運維性、是否能動態擴容以及配套設施來考慮,我們優先考慮Tair。除非目前Tair還不能支援的場合(比如分散式鎖、Hash型別的value),我們考慮用Redis。

快取穿透
一般的快取系統,都是按照key去快取查詢,如果不存在對應的value,就應該去後端系統查詢(比
如DB)。如果key對應的value是一定不存在的,並且對該key併發請求量很大,就會對後端系統造
成很大的壓力。這就叫做快取穿透。

對查詢結果為空的情況也進行快取,快取時間設定短點,或者該key對應的資料insert了之後清理快取。

快取併發
有時候如果網站併發訪問高,一個快取如果失效,可能出現多個程序同時查詢DB,同時設定快取的情況,
如果併發確實很大,這也可能造成DB壓力過大,還有快取頻繁更新的問題。

對快取查詢加鎖,如果KEY不存在,就加鎖,然後查DB入快取,然後解鎖;其他程序如果發現有鎖就
等待,然後等解鎖後返回資料或者進入DB查詢。

快取雪崩(失效)
當快取伺服器重啟或者大量快取集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)
帶來很大壓力。
不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻.

防止快取空間不夠用
① 給快取服務,選擇合適的快取逐出演算法,比如最常見的LRU。
② 針對當前設定的容量,設定適當的警戒值,比如10G的快取,當快取資料達到8G的時候,就開始發出報警,提前排查問題或者擴容。
③ 給一些沒有必要長期儲存的key,儘量設定過期時間。

我們看下圖,在WebServer(Dao層)和DB之間加一層cache,這層cache一般選取的介質是記憶體,因為我們都知道存入資料庫的資料都具有持久化的特點,那麼讀寫會有磁碟IO的操作,記憶體的讀寫速度遠比磁碟快得多。(選用儲存介質,提高訪問速度:記憶體>>磁碟;減少磁碟IO的操作,減少重複查詢,提高吞吐量)

這裡寫圖片描述

​​

常用開源的快取工具有:ehcache、memcache、redis。

ehcache 是一個純Java的程序內快取框架,hibernate使用其做二級快取。同時,ehcache可以通過多播的方式實現叢集。本人主要用於本地的快取,資料庫上層的快取。
memcache是一套分散式的快取記憶體系統,提供key-value這樣簡單的資料儲存,可充分利用CPU多核,無持久化功能。在做web叢集中可以用做session共享,頁面物件快取。
redis高效能的key-value系統,提供豐富的資料型別,單核CPU有抗併發能力,有持久化和主從複製的功能。本人主要使用redis的redis sentinel,根據不同業務分為多組。

redis注意事項
A:在增加 key 的時候儘量設定過期時間,不然 Redis Server 的記憶體使用會達到系統實體記憶體的最大值,導致 Redis 使用 VM 降低系統性能;
B:Redis Key 設計時應該儘可能短,Value 儘量不要使用複雜物件;
C:將物件轉換成 JSON 物件(利用現成的 JSON 庫)後存入 Redis;
D:將物件轉換成 Google 開源二進位制協議物件(Google Protobuf,和 JSON 資料格式類似,但是因為是二進位制表現,所以效能效率以及空間佔用都比 JSON 要小;缺點是 Protobuf 的學習曲線比 JSON 大得多);
E:Redis 使用完以後一定要釋放連線。

讀取快取中是否有相關資料,如果快取中有相關資料,則直接返回,這就是所謂的資料命中“hit”
如果快取中沒有相關資料,則從資料庫讀取相關資料,放入快取中,再返回。這就是所謂的資料未命中“miss”

快取的命中率 = 命中快取請求個數/總快取訪問請求個數 = hit/(hit+miss)
​​

NoSQL

與快取的區別
先說明一下,這裡介紹的和快取不一樣,雖然redis等也可以用來做資料儲存方案(比如Redis或者Tair),但NoSql是把它作為DB來用。如果當作DB來用,需要有效保證資料儲存方案的可用性、可靠性。

使用場景
需要結合具體的業務場景,看這塊業務涉及的資料是否適合用NoSQL來儲存,對資料的操作方式是否適合用NoSQL的方式來操作,或者是否需要用到NoSQL的一些額外特性(比如原子加減等)。
如果業務資料不需要和其他資料作關聯,不需要事務或者外來鍵之類的支援,而且有可能寫入會異常頻繁,這個時候就比較適合用NoSQL(比如HBase)。
比如,美團點評內部有一個對exception做的監控系統,如果在應用系統發生嚴重故障的時候,可能會短時間產生大量exception資料,這個時候如果選用MySQL,會造成MySQL的瞬間寫壓力飆升,容易導致MySQL伺服器的效能急劇惡化以及主從同步延遲之類的問題,這種場景就比較適合用Hbase類似的NoSQL來儲存。
檢視/儲存過程
普通業務邏輯儘量不要使用儲存過程,定時任務或報表統計函式可以根據團隊資源情況採用儲存過程處理。

GVM調優

通過監控系統(如沒有現成的系統,自己做一個簡單的上報監控的系統也很容易)上對一些機器關鍵指標(gc time、gc count、各個分代的記憶體大小變化、機器的Load值與CPU使用率、JVM的執行緒數等)的監控報警,也可以看gc log和jstat等命令的輸出,再結合線上JVM程序服務的一些關鍵介面的效能資料和請求體驗,基本上就能定位出當前的JVM是否有問題,以及是否需要調優。

非同步/多執行緒

針對某些客戶端的請求,在服務端可能需要針對這些請求做一些附屬的事情,這些事情其實使用者並不關心或者使用者不需要立即拿到這些事情的處理結果,這種情況就比較適合用非同步的方式處理這些事情。

非同步作用
A:縮短介面響應時間,使使用者的請求快速返回,使用者體驗更好。
B:避免執行緒長時間處於執行狀態,這樣會引起服務執行緒池的可用執行緒長時間不夠用,進而引起執行緒池任務佇列長度增大,從而阻塞更多請求任務,使得更多請求得不到技術處理。
C:執行緒長時間處於執行狀態,可能還會引起系統Load、CPU使用率、機器整體效能下降等一系列問題,甚至引發雪崩。非同步的思路可以在不增加機器數和CPU數的情況下,有效解決這個問題。

非同步實現
A:額外開闢執行緒,這裡可以採用額外開闢一個執行緒或者使用執行緒池的做法,在IO執行緒(處理請求響應)之外的執行緒來處理相應的任務,在IO執行緒中讓response先返回。
B:使用訊息佇列(MQ)中介軟體服務

搜尋引擎

例如:solr,elasticsearch

我在微信訂閱號等你!
這裡寫圖片描述