一:優化說明
A:有數據代表,用戶能夠承受的最大等待時間爲8秒。數據庫優化策略有不少,設計初期,創建好的數據結構對於後期性能優化相當重要。由於數據庫結構是系統的基石,基礎打很差,使用各類優化策略,也不能達到很完美的效果。mysql
B:數據庫優化的幾個方面web
能夠看出來,數據結構、SQL、索引是成本最低,且效果最好的優化手段。redis
C:性能優化是無止境的,當性能能夠知足需求時便可,不要過分優化。算法
二:優化方向
1. SQL以及索引的優化
首先要根據需求寫出結構良好的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 語句,若是隻更改一、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
————————————————版權聲明:本文爲CSDN博主「十五樓亮哥」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/u013628152/article/details/82184809