1 背景概述
在大多數的開發項目中,尤其是集成項目,都會有涉及到數據分析部分的工作,數據分析多數是各種圖表的展現和交互(所謂數據可視化),數據分析的展現速度直接影響着用戶的體驗,而且絕大多數管理系統(MES、PDM/PLM、ERP、SCM、OA、HR等等)的數據都存儲在數據庫中,數據庫相關的性能優化可以較容易提高程序系統的整體性能、提升用戶的體驗,保障項目的順利驗收。對應用程序進行整體的性能優化需要全局考慮,比如:硬件選型、軟件架構、部署架構、程序開發等方面。本文主要側重介紹程序開發部分的數據庫層面的相關優化手段,希望能對大家有所幫助。
2 預期讀者
- 數通暢聯內部員工
- 廣大IT相關從業者
3 優化途徑
無論是開發項目還是集成項目最終的目的是項目的驗收,促進項目的回款,保障公司的資金流進一步的運作。但是如果功能程序的性能不過關,響應速度慢進而影響客戶的體驗,則直接影響着項目的驗收,從而阻礙了公司的正常運作。典型的優化途徑有:硬件選選型、系統軟件、應用程序三個途徑。
3.1 硬件選型
選擇什么樣的服務器都會遇到一個相同的問題,那就是選擇什么硬件配置的服務器。在日常的項目工作中會將服務器區分為:應用服務器、數據庫服務器、文件服務器以及其他服務器。
- 應用服務器:一般用於業務系統功能的部署以及應用系統的部署服務器,對CPU、內存、要求都比較高。(推薦配置:CPU 3.0G Hz 及以上、4核及以上 內存 32G 硬盤 500G(RAID10)) 。
- 數據庫服務器:數據服務器對CPU、內存、磁盤的要求都很高,在實際應用中如果某個硬件是短板都會帶來性能問題。(推薦配置:CPU 3.0G Hz 及以上、4核及以上,內存 16G 及以上、固態硬盤 1T(RAID10))。
- 文件服務器:文件服務器主要是對IO、硬盤大小要求較高,內存較低。(推薦配置:CPU 3.0G Hz 及以上、內存 4G及以上、硬盤 2TB(RAID5))。
- 其他服務器:至於其它服務器就看各位的具體需要具體分析了。
通常情況下硬件配置越高,性能越好,但是綜合考慮(money!)硬件配置一般能滿足展望未來3-5年性能要求即可。注意:雲服務器現在也是可以考慮的選擇。
3.2 系統軟件
在操作系統的選擇上最常見的就是Linux以及Windows,考慮到服務器的性能、安全性通常我們選擇Linux操作系統。雖然Server版本操作系統本身的性能已經相對穩定,但是我們可以優化對應操作系統的配置來進一步匹配對應項目的性能需求,而Linux系列的操作系統相對來說有更多的優化策略和空間,更重要的是運維尤其遠程運維很方便。
3.3 應用程序
衡量一個程序的標准首當其沖的是程序的安全性,然后則是程序的性能,也就是程序的響應速度。對於程序的保密性要求並不是所有行業均是嚴格要求的那么對於程序的性能則是不區分行業均是更改的性能帶來更好的體驗。
應用程序的優化必殺技通常來說就是程序(軟件)本身支持水平擴展,很多書籍都有介紹,百度關鍵字:大型系統架構,可以了解很多相關知識,水平擴展是另外一個話題,這個話題也會涉及到很多方面,在本文中就不一一贅述。
系統程序的基礎環境調優對應用程序的優化也較為明顯,比如:Java程序的JVM設置、PHP程序的子進程數配置、.NET程序的認證機制、運行庫設置等等。基礎環境調優也不是本文闡述重點。在下面我們主要對軟件數據庫相關的優化方案中進行詳細介紹。
4 優化方案
雖然NoSQL也開始流行,但是更多場景下它只是數據庫的補充,數據庫自從誕生開始起就牢牢占據着管理軟件后台存儲的主場,而且從未離開。數據庫層面的性能優化屬於短平快調整就能見效、或者在開發中稍微注意就可以大幅度提升性能的常規系統調優手段。
4.1 整體策略
我們通常需要從整體策略發的角度出發,將數據庫調優從匯總查詢、視圖方式、數據緩存等三個方面來進行。
4.1.1 匯總查詢
在日常工作中如果所涉及的查詢語句較為復雜,或者需要訪問第三方的數據庫,而在訪問第三方數據庫時常常會因為不同的數據庫中不同數據表的讀取頻率不同,進而影響性能。面對這種情況我們通常將需要查詢的內容匯總到中間表,然后直接從中間表進行數據查詢。
4.1.2 視圖方式
一般情況下創建視圖是不會直接提高性能的,但是如果需要查詢的內容涉及到多個數據表之間的關聯且關聯關系較為復查,查詢出的結果集被高頻的訪問。這時如果沒有創建視圖那么每要查詢這個結果集就需要重新創建SQL,但是如果創建統一的視圖並且在創建視圖是已經進行SQL調優,方便大家的統一調用從而來提升數據庫的性能。
4.1.3 緩存方式
當前查詢結果的結果集是為展現內容提供數據展現,不是交互性數據操作,不經常被改變是我們可以將數據的查詢結果集放入緩存中,這樣在讀取時在緩存中進行獲取,減少了對數據庫的訪問操作進一步的提升程序的響應速率。在程序應用中常見的緩存處理手段如下:
靜態緩存
靜態緩存通常為創建一個靜態的HashMap 變量,在數據獲取是判斷Map中是否含有,如果有在Map變量中獲取,如果沒有則在數據庫中查詢然后放入緩存的Map變量中。
分布式緩存
分布式緩存通常是應用於集群部署的場景,通常應用部署於不同的業務服務器,通過Redis 或者Mncached來進行分布式緩存的管理。
4.2 常規優化
在數據庫優化的方案中最常見也是性能優化的最關鍵的部分就是數據庫的SQL優化,本篇文章分別在查詢優化、更新優化、其他說明三個方面來進行說明常見的SQL優化。
4.2.1 查詢優化
避免在客戶端返回大數據量
盡量避免在客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。如果一定要返回大數據量,考慮使用數據庫分頁來處理。
查詢避免使用*號
SELECT子句中避免使用*號數據庫在解析的過程中,會將*依次轉換成所有的列名,這個工作是通過查詢數據字典完成的,這意味着將耗費更多的時間。如:
Select * from emp |
應該為:
Select id,name,code from emp |
慎用DISTINCT
用EXISTS替換DISTINCT: 當提交一個包含一對多表信息(比如部門表和雇員表)的查詢時,避免在SELECT子句中使用DISTINCT. 一般可以考慮用EXIST替換, EXISTS 使查詢更為迅速,因為RDBMS核心模塊將在子查詢的條件一旦滿足后,立刻返回結果. 例子:
(低效):
SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E WHERE D.DEPT_NO = E.DEPT_NO |
(高效):
SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT ‘X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO); |
UNION和UNION-ALL
用UNION-ALL 替換UNION ( 如果有可能的話): 當SQL 語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合並, 然后在輸出最終結果前進行排序. 如果用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會因此得到提高. 需要注意的是,UNION ALL 將重復輸出兩個結果集合中相同記錄. 因此還是要從業務需求分析考慮使用UNION ALL的可行性。
條件子句的注意事項
創建索引
對where中的條件列創建索引,可以加快查詢速度。對於表中的主鍵、外鍵、有對像或身份標識意義的字段視情況添加索引。
避免null判斷
應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
select name from system_users where id is null |
最好不要給數據庫留NULL,盡可能的使用 NOT NULL填充數據庫。備注、描述、評論之類的可以設置為 NULL,其他的,最好不要使用NULL。不要以為 NULL 不需要空間,比如:char(100) 型,在字段建立時,空間就固定了, 不管是否插入值(NULL也包含在內),都是占用 100個字符的空間的,如果是varchar這樣的變長字段, null 不占用空間。
可以在id上設置默認值0,確保表中id列沒有null值,然后這樣查詢:
select name from system_users where id = 0 |
避免不等於操作
盡量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描。
避免in或not in
in 和 not in 也要慎用,否則會導致全表掃描,如:
select id from t where num in(1,2,3) |
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3 |
很多時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b) |
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num) |
避免對字段進行函數操作
盡量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如下:
select id from t where substring(name,1,3) = ’abc’ |
查詢所有以abc開頭的名字的id
應改為:
select id from t where name like 'abc%' |
4.2.2 更新優化
更新批量使用bach處理
在程序中盡量避免大量的insert或者delete同時處理,如果遇到這種情況需要使用bach進行批量統一處理。
避免大批量的insert和delete
因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。所以,如果有一個大的處理,一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)條件。
Update注意
如果只更改1、2個字段,不要Update全部字段,否則頻繁調用會引起明顯的性能消耗,同時帶來大量日志。
杜絕count(*)
select count(*) from table; |
這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。
4.2.3 事務處理
在數據庫使用中盡量減少長事務
在數據庫中如果涉及到主表、從表、附屬從表,這時如果同時操作三個數據表同時成功以及同時失敗,如果當前數據表的數據量較大,為了降低數據庫的性能壓力,我們可以采用批處理方式分別批處理三個數據表來進行數據庫性能的提升。
減少分布式事務的使用
一般的數據庫均是支持分布式事務,當涉及到跨數據庫的不同數據表的操作時我們可以使用分布式事務。但為了提高性能損耗,盡量減少這種強一致性需求,更多情況下轉化為最終一致性方式來滿足業務需求,通常來說引入消息中間件是這種場景下的常規解決手段。
4.2.4 其他說明
多用varchar和nvarchar
盡可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。
減少大字段的使用
在數據庫中定義類型是盡量避免使用大字段類型如:BLOB、TEXT、LONG以及Object等大對象的類型
不要在數據庫中存儲文件
在程序設計以及數據庫存儲是不要將圖片文件、其他日志文件的文件類型存儲於數據庫中,而是在數據庫中存儲文件索引的URL將文件存儲於文件服務器中。
4.3 配置優化
在進行數據庫連接操作時,我們可以通過選擇合適的驅動、釋放連接池中的資源、選擇符合應用場景的接口,構造只讀結果集來進一步的優化JDBC的配置。下面我們通過連接處理、匹配接口以及返回結果三個方面進行詳細的說明。
4.3.1 連接處理
對於Java程序而言, Connention的優化通常使用數據連接池(dbcp、proxool、c3p0)來進行Connention對象的管理,這樣程序的靈活性強,便於移植。但要注意的是對象池里中是沒有回收機制,並且對象池里有容量限制,對於對象池里的閑置對象盡早的釋放資源。
下面來簡單說明不用的連接池的對比:
Dbcp(DataBase connection pool):是apache上的一個 java連接池項目。
優點:配置方便,可以設置最大和最小連接,連接等待時間等,持續運行的穩定性,速度快。
缺點:沒有自動的去回收空閑連接的功能,大並發量的壓力下穩定性不高,不能夠進行連接池監控。
Proxool:Proxool是一種Java數據庫連接池技術。是sourceforge下的一個開源項目。
優點:可以設置最大和最小連接,具備監控功能。
缺點:明顯的性能問題持,續運行的穩定性不高。
C3p0:是在Hibernate和Spring中默認支持該數據庫連接池,實現了數據源和jndi綁定,支持jdbc3規范和jdbc2的標准擴展。
優點:支持高並發,異步操作,有自動回收空閑連接功能。
缺點:沒有Dbcp的速度快。
4.3.2 匹配接口
對於Statement對象的優化,我們需要根據不同的應用場合選擇合適的Statement接口。如:
Statement:不帶參數,例如:查詢時,不需要到任何參數。
PreparedStatement: PreparedStatement可以寫參數化查詢,比Statement能獲得更好的性能,可以阻止常見的SQL注入式攻擊,提高安全性。
CallableStatement:專門針對存儲過程,使用它能享受到所有存儲過程帶來的優勢,但也包括存儲過程帶來的劣勢如Java程序可移植性查,依賴數據庫等。
4.3.3 返回結果
優化結果集(ResultSet)查詢時候,返回的結果集有不同的類型。結果集分兩種類型:只讀和可更改。返回的結果集默認就是只讀的。而在Oracle中我們可以設置手工加鎖語句(Select XXX forUpdate)。
明確指定主鍵,並且有此數據則鎖定若無則不鎖定
SELECT * FROM products WHERE id='3' FOR UPDATE; |
無主鍵或者主鍵不明確則進行表鎖定
SELECT * FROM products WHERE name='Mouse' FOR UPDATE; |
5 個人總結
應用程序優化是一個系統工程,需要綜合考慮,更多時候要提前考慮,在系統架構層面來保障系統具有更多優化的能力。系統運維有一種消極的說法,系統能用就行,不要輕易去改變;但對於系統開發而言,每一次代碼重構都是一次系統調優以及增強調優能力的機會。Devops也慢慢開始盛行了,開發和運營越來越密切,甚至是一套班子兩種角色,你(們)如何選擇?我個人而言,傾向主動調優、擁抱變化,即便可能帶來一些風險。
無論是對公司的產品進行開發還是在項目開發的過程中,要在全局的角度出發整體考慮、制定規范、落實到每一項的工作中,從制度上保障系統性能調優的能力。筆者作為數通暢聯公司的一名技術員工,今天將自己所學所用的常見的數據庫優化相關處理總結出來與大家分享。如果對本文檔相關的描述信息存在疑問歡迎加入數通暢聯官方技術群(299719834)進行討論。