MySQL調優之查詢優化

PHPer技術棧發表於2021-11-09

1、網路

(1)網路丟包,重傳

這個比較容易理解。當SQL 從客戶端傳送到資料庫,執行完畢,資料庫將結果返回給客戶端,這個將資料返回給客戶端的過程本質是網路包傳輸。因為鏈路的不穩定性,如果在傳輸過程中傳送丟包會導致資料包重傳,進而增加資料傳輸時間。從客戶端來看,就會覺得SQL 執行慢。

(2)網路卡滿 比如大欄位

如果公司業務體量很大,比如平時每天300w訂單的電商平臺,平臺大促(雙十一,618)的時候極有可能出現網路卡被打滿。網路卡頻寬被佔滿類似各種節假日高速公路收費站(網路卡)擁堵導致車流(資料包傳輸的速度)行動緩慢。

(3)網路鏈路變長

該場景會影響應用緯度的一個事務比如交易下單整體耗時。

我們知道每個節點之間的資料傳輸是需要時間的,比如同城跨機房(15KM)之間的訪問一般網路耗時1.5ms左右。

鏈路1 [client1]–呼叫–[client2]—[proxy]—[db] 相比 鏈路2[client1] – [proxy] –[db]

執行一條sql請求會增加 [client1]–[client2]之間的網路傳輸耗時大約3ms。如果一個業務事件包含30個sql ,那麼鏈路1要比鏈路2 多花至少90ms的時間成本。導致業務整體變慢。

2、CPU

(1)cpu 電源策略

cpu 電源策略是控制cpu執行在哪種模式下的耗電策略的,對於資料庫伺服器推薦最大效能模式 以下內容摘自 《Red Hat Enterprise Linux7 電源管理指南》

ASPMActive-State Power Management,活動狀態電源管理)能節省 PCI Express(PCIe,Peripheral Component Interconnect Express)子系統的電量,其原理為當 PCIe 連線沒有處於使用狀態時將其設定為低功率狀態。ASPM 可以同時控制連線兩端的電源狀態,並且在連線終端的裝置處於滿電狀態的情況下,仍然可以節電。

啟用 ASPM 時,在不同電源狀態間轉換連線時需要時間,因此會增加裝置延遲。ASPM 有三種決定電源狀態的策略:

  • 預設(default)

    根據系統韌體(例如:BIOS)指定的預設設定設定 PCIe 連線的電源狀態。這是 ASPM 的預設狀態。

  • 節電(powersave)

    將 ASPM 設定為儘可能節電,不考慮效能損失。

  • 效能(performance)

    禁用 ASPM 使 PCIe 連結以最佳效能操作。

使用 pcie_aspm kernel 引數可以啟用或者禁用 ASPM,其中 pcie_aspm=off 會禁用 ASPM,而 pcie_aspm=force 會啟用 ASPM,即使在不支援 ASPM 的裝置中也可以。

ASPM 策略在 /sys/module/pcie_aspm/parameters/policy 中設定,但還可以使用 pcie_aspm.policy kernel 引數在啟動時指定,其中 pcie_aspm.policy=performance 將設定 ASPM 效能策略。

(2)CPU被消耗

CPU的消耗如圖:

使用者

使用者空間CPU消耗,各種邏輯運算

正在進行大量tps

函式/排序/型別轉化/邏輯IO訪問…

使用者空間消耗大量cpu,產生的系統呼叫是什麼?那些函式使用了cpu週期?

IO等待

等待IO請求的完成

此時CPU實際上空閒

如vmstat中的wa 很高。但IO等待增加,wa也不一定會上升(請求I/O後等待響應,但程式從核上移開了)

3、IO

(1)磁碟IO被其他任務佔用

有些備份策略為了減少備份空間的使用,基於xtrabckup備份的時候 使用了compress選項將備份集壓縮。當我們需要在資料庫伺服器上恢復一個比較大的例項,而解壓縮的過程需要耗費cpu和佔用大量io導致資料庫例項所在的磁碟io使用率100%,會影響MySQL 從磁碟獲取資料的速度,導致大量慢查詢。

(2)raid卡 充放電,raid 卡重置

RAID卡都有寫cache(Battery Backed Write Cache),寫cache對IO效能的提升非常明顯,因為掉電會丟失資料,所以必須由電池提供支援。電池會定期充放電,一般為90天左右,當發現電量低於某個閥值時,會將寫cache策略從writeback置為writethrough,相當於寫cache會失效,這時如果系統有大量的IO操作,可能會明顯感覺到IO響應速度變慢,cpu 佇列堆積系統load飆高。

(3)IO排程演算法

noop(電梯式排程策略):

NOOP實現了一個FIFO佇列,它像電梯的工作方式一樣對I/O請求進行組織,當有一個新的請求到來時,它將請求合併到最近的請求之後,以此來保證請求同一個介質。NOOP傾向於餓死讀而利於寫,因此NOOP對於快閃記憶體裝置,RAM以及嵌入式是最好的選擇。

deadline(介質時間排程策略):

Deadline確保了在一個截至時間內服務請求,這個截至時間是可調整的,而預設讀期限短於寫期限。這樣就防止了寫操作因為不能被讀取而餓死的現象。Deadline對資料庫類應用是最好的選擇。

anticipatory(預料I/O排程策略):

本質上與Deadline一樣,但在最後一次讀操作後,要等待6ms,才能繼續進行對其他I/O請求進行排程。它會在每個6ms中插入新的I/O操作,而會將一些小寫入流合併成一個大寫入流,用寫入延時換取最大的寫入吞吐量。AS適合於寫入較多的環境,比如檔案伺服器,AS對資料庫環境表現很差。

4、上下文切換

(1)什麼叫上下文切換?

假設有三個CPU,每個CPU核心數是4,那麼物理核心數:3*4 = 12(個)

這個核是指CPU裡面的Core,可以理解為是將四個CPU封裝在一起,但是有的CPU具有超執行緒的技術,一個核可以模擬出兩個兩個虛擬核,上面的12個物理核可以讓作業系統認為有24個核,同是並行執行24個任務,注意:實際上一個核(Core)只能同時執行一個任務,多個任務同時執行的話,會在Core上高速切換。

為什麼要超執行緒?

超執行緒這個概念是Intel提出的,簡單來說是在一個CPU上真正的併發兩個執行緒,聽起來似乎不太可能,因為CPU都是分時的啊,其實這裡也是分時,因為前面也提到一個CPU除了處理器核心還有其他裝置,一段程式碼執行過程也不光是隻有處理器核心工作,如果兩個執行緒A和B,A正在使用處理器核心,B正在使用快取或者其他裝置,那AB兩個執行緒就可以併發執行,但是如果AB都在訪問同一個裝置,那就只能等前一個執行緒執行完後一個執行緒才能執行。實現這種併發的原理是 在CPU里加了一個協調輔助核心,根據Intel提供的資料,這樣一個裝置會使得裝置面積增大5%,但是效能提高15%~30%。

執行緒切換,涉及到一個問題,如果CPU在ABA這樣切換的話,第二次執行到A,如何確定從哪兒開始執行呢?

在每個任務執行前,CPU 都需要知道任務從哪裡載入、又從哪裡開始執行,也就是說,需要系統事先幫它設定好CPU 暫存器和程式計數器(Program Counter,PC)。

CPU 暫存器,是 CPU 內建的容量小、但速度極快的記憶體。而程式計數器,則是用來儲存 CPU 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 CPU 在執行任何任務前,必須的依賴環境,因此也被叫做CPU 上下文。

CPU 上下文切換,就是先把前一個任務的 CPU 上下文(也就是 CPU 暫存器和程式計數器)儲存起來,然後載入新任務的上下文到這些暫存器和程式計數器,最後再跳轉到程式計數器所指的新位置,執行新任務。

(2)暫存器為什麼會影響CPU 效能?

CPU 上下文切換無非就是更新了 CPU 暫存器的值,但這些暫存器,本身就是為了快速執行任務而設計的,為什麼會影響系統的 CPU 效能呢?

我們都知道執行緒上下文切換,這兒的執行緒是指任務,任務包括哪些呢?執行緒以及程式,還有其他的嗎?硬體通過觸發訊號,會導致中斷處理程式的呼叫,也是一種常見的任務。

所以根據任務的不同,CPU 的上下文切換就可以分為程式上下文切換、執行緒上下文切換以及中斷上下文切換。

(3)程式上下文切換

Linux 按照特權等級,把程式的執行空間分為核心空間和使用者空間,分別對應著下圖中, CPU 特權等級的 Ring 0 和 Ring 3。

  • 核心空間(Ring 0)具有最高許可權,可以直接訪問所有資源;
  • 使用者空間(Ring 3)只能訪問受限資源,不能直接訪問記憶體等硬體裝置,必須通過系統呼叫陷入到核心中,才能訪問這些特權資源。

換個角度看,也就是說,程式既可以在使用者空間執行,又可以在核心空間中執行。程式在使用者空間執行時,被稱為程式的使用者態,而陷入核心空間的時候,被稱為程式的核心態。從使用者態到核心態的轉變,需要通過系統呼叫來完成。核心空間態資源包括核心的堆疊、暫存器等;使用者空間態資源包括虛擬記憶體、棧、變數、正文、資料等。

(4)執行緒上下文切換

在切換時,虛擬記憶體這些資源就保持不動,只需要切換執行緒的私有資料、暫存器等不共享的資料

程式是作業系統的管理單位,而執行緒則是程式的管理單位;一個程式至少包含一個執行執行緒。不管是在單執行緒還是多執行緒中,每個執行緒都有一個程式計數器(記錄要執行的下一條指令),一組暫存器(儲存當前執行緒的工作變數),堆疊(記錄執行歷史,其中每一幀儲存了一個已經呼叫但未返回的過程)。雖然執行緒寄生在程式中,但與他的程式是不同的概念,並且可以分別處理:程式是系統分配資源的基本單位,執行緒是排程CPU的基本單位

一個執行緒指的是程式中一個單一順序的控制流,一個程式中可以並行多個執行緒,每條執行緒並行執行不同的任務。每個執行緒共享堆空間,擁有自己獨立的棧空間

  1. 執行緒劃分尺度小於程式,執行緒隸屬於某個程式;
  2. 程式是CPU、記憶體等資源佔用的基本單位,執行緒是不能獨立佔有這些資源的;
  3. 程式之間相互獨立,通訊比較困難,而執行緒之間共享一塊記憶體區域,通訊方便;
  4. 程式在執行過程中,包含:固定的入口、執行順序和出口而程式的這些過程會被應用程式控制

(5)中斷上下文切換

為了快速響應硬體的事件,中斷處理會打斷程式的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件。而在打斷其他程式時,就需要將程式當前的狀態儲存下來,這樣在中斷結束後,程式仍然可以從原來的狀態恢復執行。
跟程式上下文不同,中斷上下文切換並不涉及到程式的使用者態。所以,即便中斷過程打斷了一個正處在使用者態的程式,也不需要儲存和恢復這個程式的虛擬記憶體、全域性變數等使用者態資源。

中斷上下文,其實只包括核心態中斷服務程式執行所必需的狀態,包括 CPU 暫存器、核心堆疊、硬體中斷引數等。

對同一個 CPU 來說,中斷處理比程式擁有更高的優先順序,所以中斷上下文切換並不會與程式上下文切換同時發生。同樣道理,由於中斷會打斷正常程式的排程和執行,所以大部分中斷處理程式都短小精悍,以便儘可能快的執行結束。

另外,跟程式上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數過多也會耗費大量的 CPU,甚至嚴重降低系統的整體效能。所以,當你發現中斷次數過多時,就需要注意去排查它是否會給你的系統帶來嚴重的效能問題。

5、系統呼叫

(1)系統呼叫是什麼?

定義:使用者在程式設計時,可以呼叫的作業系統功能。全稱:作業系統功能呼叫。從使用者態進入核心態。

具體來說:當你的程式碼需要做IO操作(open、read、write)、或者是進行記憶體操作(mmpa、sbrk)、甚至是說要獲取一個系統時間(gettimeofday),就需要通過系統呼叫來和核心進行互動。只要程式是建立在Linux核心之上的,就繞不開系統呼叫。

作業系統的主要功能是為管理硬體資源和為應用程式開發人員提供良好的環境來使應用程式具有更好的相容性。

為了達到這個目的,核心提供一系列具備預定功能的多核心函式,通過一組稱為系統呼叫(system call)的介面呈現給使用者。系統呼叫把應用程式的請求傳給核心,呼叫相應的的核心函式完成所需的處理,將處理結果返回給應用程式。

現代的作業系統通常都具有多工處理的功能,通常靠程式來實現。由於作業系統快速的在每個程式間切換執行,所以一切看起來就會像是同時的。同時這也帶來了很多安全問題。因此作業系統必須保證每一個程式都能安全的執行。這一問題的解決方法是在處理器中加入基址暫存器界限暫存器。這兩個暫存器中的內容用硬體限制了對儲存器的存取指令所訪問的儲存器的地址。這樣就可以在系統切換程式時寫入這兩個暫存器的內容到該程式被分配的地址範圍,從而避免惡意軟體。  

為了防止使用者程式修改基址暫存器和界限暫存器中的內容來達到訪問其他記憶體空間的目的,這兩個暫存器必須通過一些特殊的指令來訪問。

通常,處理器設有兩種模式:“使用者模式”與“核心模式”,通過一個標籤位來鑑別當前正處於什麼模式。一些諸如修改基址暫存器內容的指令只有在核心模式中可以執行,而處於使用者模式的時候硬體會直接跳過這個指令並繼續執行下一個。

同樣,為了安全問題,一些I/O操作的指令都被限制在只有核心模式可以執行,因此作業系統有必要提供介面來為應用程式提供諸如讀取磁碟某位置的資料的介面,這些介面就被稱為系統呼叫

  ### (2)為什麼系統呼叫會影響效能

當作業系統接收到系統呼叫請求後,會讓處理器進入核心模式,從而執行諸如I/O操作,修改基址暫存器內容等指令,而當處理完系統呼叫內容後,作業系統會讓處理器返回使用者模式,來執行使用者程式碼。

系統呼叫必須通過軟中斷機制(異常處理機制)首先進入系統核心,然後才能轉向相應的命令處理程式。

在採用搶先式排程的系統中,當系統呼叫返回時,要重新進行排程分析――是否有更高優先順序的任務就緒。

系統呼叫的呼叫過程和被呼叫過程執行在不同的狀態。

系統呼叫花在核心態使用者態的切換上的時間是差不多的,但區別在於不同的系統呼叫當進入到核心態之後要處理的工作不同,呆在核心態裡的時候相差較大。

6、生成統計資訊

(1)統計資訊概念

MySQL統計資訊是指資料庫通過取樣、統計出來的表、索引的相關資訊,例如,表的記錄數、聚集索引page個數、欄位的Cardinality….。MySQL在生成執行計劃時,需要根據索引的統計資訊進行估算,計算出最低代價(或者說是最小開銷)的執行計劃.MySQL支援有限的索引統計資訊,因儲存引擎不同而統計資訊收集的方式也不同. MySQL官方關於統計資訊的概念介紹幾乎等同於無,不過對於已經接觸過其它型別資料庫的同學而言,理解這個概念應該不在話下。相對於其它資料庫而言,MySQL統計資訊無法手工刪除。MySQL 8.0之前的版本,MySQL是沒有直方圖的。

詳細介紹參考:MySQL的統計資訊學習總結

7、鎖等待時間

資料被其他訪問鎖住了,等待鎖釋放

查詢效能低下的主要原因是訪問的資料太多,某些查詢不可避免的需要篩選大量的資料,我們可以通過減少訪問資料量的方式進行優化

主要就是看是否向資料庫請求了不需要的資料

1、減少請求的資料量

1.只返回必要的列:最好不要使用 SELECT * 語句。
2.只返回必要的行:使用 LIMIT 語句來限制返回的資料。
3.快取重複查詢的資料:使用快取可以避免在資料庫中進行查詢,特別在要查詢的資料經常被重複查詢時,快取帶來的查詢效能提升將會是非常明顯的。

2、減少伺服器端掃描的行數

例如使用索引來覆蓋查詢。

3、切分大查詢

一個大查詢如果一次性執行的話,可能一次鎖住很多資料、佔滿整個事務日誌、耗盡系統資源、阻塞很多小的但重要的查詢。

DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);

rows_affected = 0 do { rows_affected = do_query( "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000") } while rows_affected > 0

4、分解大連線查詢

將一個大連線查詢分解成對每一個表進行一次單表查詢,然後在應用程式中進行關聯,這樣做的好處有:

1.讓快取更高效。對於連線查詢,如果其中一個表發生變化,那麼整個查詢快取就無法使用。而分解後的多個查詢,即使其中一個表發生變化,對其它表的查詢快取依然可以使用。
2.分解成多個單表查詢,這些單表查詢的快取結果更可能被其它查詢使用到,從而減少冗餘記錄的查詢。
3.減少鎖競爭;
4.在應用層進行連線,可以更容易對資料庫進行拆分,從而更容易做到高效能和可伸縮。
5.查詢本身效率也可能會有所提升。例如下面的例子中,使用 IN() 代替連線查詢,可以讓 MySQL 按照 ID 順序進行查詢,這可能比隨機的連線要更高效。

SELECT * FROM tag JOIN tag_post ON tag_post.tag_id=tag.id JOIN post ON tag_post.post_id=post.id WHERE tag.tag='mysql';

SELECT * FROM tag WHERE tag='mysql'; SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);

1、查詢快取

在解析一個查詢語句之前,如果查詢快取是開啟的,那麼mysql會優先檢查這個查詢是否命中查詢快取中的資料,如果查詢恰好命中了查詢快取,那麼會在返回結果之前會檢查使用者許可權,如果許可權沒有問題,那麼mysql會跳過所有的階段,就直接從快取中拿到結果並返回給客戶端

2、查詢優化處理

mysql查詢完快取之後會經過以下幾個步驟:解析SQL、預處理、優化SQL執行計劃,這幾個步驟出現任何的錯誤,都可能會終止查詢

(1)、語法解析器和預處理

mysql通過關鍵字將SQL語句進行解析,並生成一顆解析樹,mysql解析器將使用mysql語法規則驗證和解析查詢,例如驗證使用使用了錯誤的關鍵字或者順序是否正確等等,前處理器會進一步檢查解析樹是否合法,例如表名和列名是否存在,是否有歧義,還會驗證許可權等等

(2)、查詢優化器

當語法樹沒有問題之後,相應的要由優化器將其轉成執行計劃,一條查詢語句可以使用非常多的執行方式,最後都可以得到對應的結果,但是不同的執行方式帶來的效率是不同的,優化器的最主要目的就是要選擇最有效的執行計劃。

mysql使用的是基於成本的優化器(還有一個基於規則的優化器),在優化的時候會嘗試預測一個查詢使用某種查詢計劃時候的成本,並選擇其中成本最小的一個。

3、查詢優化器

select count(*) from film_actor;
show status like ‘last_query_cost’;
可以看到這條查詢語句大概需要做1104個資料頁才能找到對應的資料,這是經過一系列的統計資訊計算來的

這些資訊包括:

  • 每個表或者索引的頁面個數
  • 索引的基數
  • 索引和資料行的長度
  • 索引的分佈情況

索引基數cardinality):索引中不重複的索引值的數量; 例如,某個資料列包含值1、3、7、4、7、3,那麼它的基數就是4。

(1)優化器的優化策略

靜態優化

直接對解析樹進行分析,並完成優化

動態優化

動態優化與查詢的上下文有關,也可能跟取值、索引對應的行數有關

注意

mysql對查詢的靜態優化只需要一次,但對動態優化在每次執行時都需要重新評估

(2)優化器的優化型別

  1. 重新定義關聯表的順序

    資料表的關聯並不總是按照在查詢中指定的順序進行,決定關聯順序時優化器很重要的功能

  2. 將外連線轉化成內連線,內連線的效率要高於外連線

  3. 使用等價變換規則,mysql可以使用一些等價變化來簡化並規劃表示式

  4. 優化count(),min(),max()

    索引和列是否可以為空通常可以幫助mysql優化這類表示式:例如,要找到某一列的最小值,只需要查詢索引的最左端的記錄即可,不需要全文掃描比較

  5. 預估並轉化為常數表示式,當mysql檢測到一個表示式可以轉化為常數的時候,就會一直把該表示式作為常數進行處理

    explain select film.film_id,film_actor.actor_id from film inner join film_actor using(film_id) where film.film_id = 1

  6. 索引覆蓋掃描,當索引中的列包含所有查詢中需要使用的列的時候,可以使用覆蓋索引

  7. 子查詢優化

    mysql在某些情況下可以將子查詢轉換一種效率更高的形式,從而減少多個查詢多次對資料進行訪問,例如將經常查詢的資料放入到快取中

  8. 等值傳播

    如果兩個列的值通過等式關聯,那麼mysql能夠把其中一個列的where條件傳遞到另一個上:

    `explain select film.film_id from film inner join film_actor using(film_id

    ) where film.film_id > 500;`

    這裡使用film_id欄位進行等值關聯,film_id這個列不僅適用於film表而且適用於film_actor表

    `explain select film.film_id from film inner join film_actor using(film_id

    ) where film.film_id > 500 and film_actor.film_id > 500;`

(3)關聯查詢

mysql的關聯查詢很重要,但其實關聯查詢執行的策略比較簡單:mysql對任何關聯都執行巢狀迴圈關聯操作,即mysql先在一張表中迴圈取出單條資料,然後再巢狀到下一個表中尋找匹配的行,依次下去,直到找到所有表中匹配的行為止。然後根據各個表匹配的行,返回查詢中需要的各個列。mysql會嘗試再最後一個關聯表中找到所有匹配的行,如果最後一個關聯表無法找到更多的行之後,mysql返回到上一層次關聯表,看是否能夠找到更多的匹配記錄,以此類推迭代執行。整體的思路如此,但是要注意實際的執行過程中有多個變種形式

join的實現方式原理

案例演示

檢視不同的順序執行方式對查詢效能的影響:

explain select film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id);

檢視執行的成本:

show status like 'last_query_cost';

按照自己預想的規定順序執行:

explain select straight_join film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id);

檢視執行的成本:

show status like 'last_query_cost';

(4)排序優化

無論如何排序都是一個成本很高的操作,所以從效能的角度出發,應該儘可能避免排序或者儘可能避免對大量資料進行排序。

推薦使用利用索引進行排序,但是當不能使用索引的時候,mysql就需要自己進行排序,如果資料量小則再記憶體中進行,如果資料量大就需要使用磁碟,mysql中稱之為filesort。

如果需要排序的資料量小於排序緩衝區(show variables like ‘%sort_buffer_size%’?,mysql使用記憶體進行快速排序操作,如果記憶體不夠排序,那麼mysql就會先將樹分塊,對每個獨立的塊使用快速排序進行排序,並將各個塊的排序結果存放再磁碟上,然後將各個排好序的塊進行合併,最後返回排序結果

兩次傳輸排序

第一次資料讀取是將需要排序的欄位讀取出來,然後進行排序,第二次是將排好序的結果按照需要去讀取資料行。

這種方式效率比較低,原因是第二次讀取資料的時候因為已經排好序,需要去讀取所有記錄而此時更多的是隨機IO,讀取資料成本會比較高

兩次傳輸的優勢,在排序的時候儲存儘可能少的資料,讓排序緩衝區可以儘可能多的容納行數來進行排序操作

單次傳輸排序

先讀取查詢所需要的所有列,然後再根據給定列進行排序,最後直接返回排序結果,此方式只需要一次順序IO讀取所有的資料,而無須任何的隨機IO,問題在於查詢的列特別多的時候,會佔用大量的儲存空間,無法儲存大量的資料

注意

當需要排序的列的總大小超過max_length_for_sort_data定義的位元組,mysql會選擇雙次排序,反之使用單次排序,當然,使用者可以設定此引數的值來選擇排序的方式

1、優化count()查詢

count()是特殊的函式,有兩種不同的作用,一種是某個列值的數量,也可以統計行數

(1).使用近似值

在某些應用場景中,不需要完全精確的值,可以參考使用近似值來代替,比如可以使用explain來獲取近似的值

其實在很多OLAP的應用中,需要計算某一個列值的基數,有一個計算近似值的演算法叫hyperloglog。

(2).更復雜的優化

一般情況下,count()需要掃描大量的行才能獲取精確的資料,其實很難優化,在實際操作的時候可以考慮使用索引覆蓋掃描,或者增加彙總表,或者增加外部快取系統。

注意:

總有人認為myisam的count函式比較快,這是有前提條件的,只有沒有任何where條件的count(*)才是比較快的

2、優化關聯查詢

確保on或者using子句中的列上有索引,在建立索引的時候就要考慮到關聯的順序

當表A和表B使用列C關聯的時候,如果優化器的關聯順序是B、A,那麼就不需要再B表的對應列上建上索引,沒有用到的索引只會帶來額外的負擔,一般情況下來說,只需要在關聯順序中的第二個表的相應列上建立索引

確保任何的groupby和order by中的表示式只涉及到一個表中的列,這樣mysql才有可能使用索引來優化這個過程

3、優化子查詢

子查詢的優化最重要的優化建議是儘可能使用關聯查詢代替

4、優化limit分頁

  1. 在很多應用場景中我們需要將資料進行分頁,一般會使用limit加上偏移量的方法實現,同時加上合適的orderby 的子句,如果這種方式有索引的幫助,效率通常不錯,否則的化需要進行大量的檔案排序操作,還有一種情況,當偏移量非常大的時候,前面的大部分資料都會被拋棄,這樣的代價太高。
  2. 要優化這種查詢的話,要麼是在頁面中限制分頁的數量,要麼優化大偏移量的效能
  3. 優化此類查詢的最簡單的辦法就是儘可能地使用覆蓋索引,而不是查詢所有的列(檢視執行計劃檢視掃描的行數)

select film_id,description from film order by title limit 50,5

explain select film.film_id,film.description from film inner join (select film_id from film order by title limit 50,5) as lim using(film_id);

5、優化union查詢

mysql總是通過建立並填充臨時表的方式來執行union查詢,因此很多優化策略在union查詢中都沒法很好的使用。經常需要手工的將where、limit、order by等子句下推到各個子查詢中,以便優化器可以充分利用這些條件進行優化

除非確實需要伺服器消除重複的行,否則一定要使用union all,因此沒有all關鍵字,mysql會在查詢的時候給臨時表加上distinct的關鍵字,這個操作的代價很高

6、推薦使用使用者自定義變數

使用者自定義變數是一個容易被遺忘的mysql特性,但是如果能夠用好,在某些場景下可以寫出非常高效的查詢語句,在查詢中混合使用過程化和關係話邏輯的時候,自定義變數會非常有用。

使用者自定義變數是一個用來儲存內容的臨時容器,在連線mysql的整個過程中都存在。

自定義變數的使用

set @one :=1

set @min_actor :=(select min(actor_id) from actor)

set @last_week :=current_date-interval 1 week;

自定義變數的限制

1、無法使用查詢快取

2、不能在使用常量或者識別符號的地方使用自定義變數,例如表名、列名或者limit子句

3、使用者自定義變數的生命週期是在一個連線中有效,所以不能用它們來做連線間的通訊

4、不能顯式地宣告自定義變數地型別

5、mysql優化器在某些場景下可能會將這些變數優化掉,這可能導致程式碼不按預想地方式執行

6、賦值符號:=的優先順序非常低,所以在使用賦值表示式的時候應該明確的使用括號

7、使用未定義變數不會產生任何語法錯誤

自定義變數的使用案例

優化排名語句

1、在給一個變數賦值的同時使用這個變數

2、查詢獲取演過最多電影的前10名演員,然後根據出演電影次數做一個排名

避免重新查詢剛剛更新的資料

當需要高效的更新一條記錄的時間戳,同時希望查詢當前記錄中存放的時間戳是什麼

update t1 set lastUpdated=now() where id =1; select lastUpdated from t1 where id =1;

update t1 set lastupdated = now() where id = 1 and @now:=now(); select @now;

確定取值的順序

在賦值和讀取變數的時候可能是在查詢的不同階段

set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1;

因為where和select在查詢的不同階段執行,所以看到查詢到兩條記錄,這不符合預期

set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1 order by first_name

當引入了orde;r by之後,發現列印出了全部結果,這是因為order by引入了檔案排序,而where條件是在檔案排序操作之前取值的

解決這個問題的關鍵在於讓變數的賦值和取值發生在執行查詢的同一階段:

set @rownum:=0; select actor_id,@rownum as cnt from actor where (@rownum:=@rownum+1)<=1;

本作品採用《CC 協議》,轉載必須註明作者和本文連結
PHPer技術棧

相關文章