公司有一套Web系統, 使用方反饋系統某些頁面訪問速度緩慢, 用戶體驗不好, 而且偶爾還會出現HTTP 502錯誤。html
這是典型的服務器端IO阻塞引起的問題,經過對訪問頁面的程序邏輯進行跟蹤,發現問題應該是出在某個SQL查詢上。程序員
在頁面程序運行的某個步驟中,有這樣一段SQLweb
select distinct(server) from user_record where type = 'GD0001'
user_record表中的數據大概有2000萬條左右 , 字段type的值爲GD0001的記錄大概有500萬,而這段SQL執行的結果大概有30多條。type字段上有索引,可是SQL語句的執行時間卻要超過一分鐘。數據庫
獲得去重後server字段的值是致使頁面訪問緩慢的根本緣由。服務器
根據程序的要求, server字段的值須要實時求得,因此當初在設計程序的時候纔會使用這段SQL去得到結果。數據量少的時候,不會出現問題,然而, 數據增加的速度超出當初的預期,因而就致使了性能問題的出現。工具
要解決這個問題不難,由於server字段值的範圍相對是穩定的,能夠想辦法把值提取出來放到一個冗餘的表裏面,而且經過某種機制讓這個新表的值與原表中server字段的值保持同步,查的時候查這個新表, 這樣訪問速度緩慢的問題也就迎刃而解了。性能
顯然,使用這種方案解決問題須要不小的工做量。要使解決這個問題的成本最小化,最好的方法是優化這個查詢,假如本來這個查詢運行的時間是一分鐘,那麼能使運行這個查詢的時間降低至一秒,問題也算解決。優化
這個目標看起來彷佛難以實現,事實上倒是能夠作到的。spa
select distinct(server) from user_record where type = 'GD0001'
由於這段SQL語句的篩選條件type字段有索引,因此整個SQL語句的邏輯查詢步驟大體以下設計
經過type索引篩選出符合要求記錄的主鍵字段的標識
經過主鍵標識定位到表中記錄的源數據
拿到字段的值進行distinct去重獲得最終的結果。
上面的三個步驟中,最消耗性能的是第二步。由於索引和表的實際數據實際上是分開放置的,大概的樣子以下面這個圖。圖中長的最大的那個其實就是數據表,表中全部的數據都在上面,只是看起來不像一張「表」而已。
第二步是經過索引篩選出符合條件的記錄的主鍵標識定位到實際數據,過程大概以下面這張圖
想象一下, 要優化的那段SQL,而type值爲GD0001記錄有500萬條, 就算MySQL不會蠢到去查500萬次才能獲得結果,但也確定不是輕輕鬆鬆就能完成的。 若是能優化掉這一步,整個查詢的開銷也就下去了。
select distinct(server) from user_record where type = 'GD0001'
對於這段SQL,咱們的目標是並非獲得全部字段的值,僅僅server字段的值就足夠了。
假如咱們把server字段的值放在type字段的索引裏,那麼在第一步查索引的時候就能獲得第二步的結果。執行過程以下圖
在關係數據庫中,有一種索引稱爲覆蓋索引,就是爲了知足這種優化需求而設計的。
針對這段SQL語句優化的覆蓋索引建立語句以下
create index index_type_server on user_record(type, server)
這個索引建立語句會將type和server兩個字段的值組織在一個索引裏面, 所以當
select distinct(server) from user_record where type = 'GD0001'
全部的查詢步驟在索引中就能完成,而不用再去源數據表裏提取數據,也就是在沒創建這個索引時進行查詢的第二步被消除了,所以查詢的性能極大幅度的獲得了提高。
在沒創建覆蓋索引前,查詢的時間須要一分鐘以上,在創建索引後,查詢的時間降低到幾百毫秒的級別。本來網頁加載緩慢和偶爾報HTTP 502錯誤失去響應的問題也獲得瞭解決。
讓SQL語句合理的利用索引快速的獲得查詢結果是一門學問,值得深究。 合理利用索引,能讓對程序性能的優化從代碼層面轉移到數據庫層面, 讓問題由最適合解決的工具和手段去解決,物盡其用,如此不但能減小代碼複雜度,還能提升解決問題的效率。這是一個程序員必需要具有的一種技能。