1. 程式人生 > 其它 >節選-《資料庫高效優化》架構、規範與SQL技巧一書 有關SQL優化、索引、規範等

節選-《資料庫高效優化》架構、規範與SQL技巧一書 有關SQL優化、索引、規範等

SQL優化

對於普通的採用數值型別的欄位,範圍查詢就是正常的索引範圍掃描,執行效率很高。
對於文字型別欄位的表,範圍查詢就是對應的全表掃描,效率較低是顯而易見的。
(分析)字元型別在索引中是“亂序”的,這是因為字元型別的排序方式與我們的預期不同,從“select * from t2 where id>='3199990'”執行返回的多條資料記錄可見,不是直觀上的10條記錄,這也是當初在做表設計時,開發人員沒有注意到的問題。
字元型別還導致了聚簇因子很大,原因就是插入順序與排序順序不同,詳細點說,就是按照數字型別插入,(1...3200000),按字元型別(‘1’...‘32000000’)t排序

在對字元型別使用大於運算子時,會導致優化器認為需要掃描索引大部分資料且聚簇因子很大,最終導致棄用索引掃描而改用全表掃描方式。

糟糕的資料結構設計往往是致命的,後期的優化只是補救措施。只有從源頭上加以杜絕,才是優化的根本。
在設計初期能引入資料庫稽核,可以起到很好的作用。

由於資訊的缺失導致了優化器產生了較差的執行計劃。
統計資訊是優化器優化的重要參考依據。

常數索引在儲存密度上要高於普通欄位索引,因此掃描塊數更少,耗時更少。

資料庫聚簇因子 (多在Oracle)
聚簇因子是Oracle統計資訊中在CBO優化器模式下用於計算cost的引數之一,決定了當前SQL語句是否走索引,還是全表掃描以及是否作為巢狀連線外部表等,如此這般,那到底什麼是聚簇因子,那些情況會影響聚簇因子,以及如何提高聚簇因子?
https://blog.csdn.net/weixin_30615567/article/details/113557187

(公司不太用oracle暫時不看)

  • 優化SQL就是一個抽絲剝繭找到問題本質的過程。在不斷猜測、不斷試錯的過程中,逐步接近事件的本質。你所掌握的知識點越多,可“猜測”的可能性就越多。

  • 資料結構的變更要經過DBA的稽核,這樣可以避免很多問題,也可以儘早發現問題、解決問題。

MySQL執行計劃

MySQL執行計劃的定義

在MySQL中可以通過explain關鍵字模擬優化器執行SQL語句,從而知道MySQL是如何處理SQL語句的。
MySQL整個查詢的過程

  • 客戶端向MySQL伺服器傳送一條查詢請求
  • 伺服器首先檢查查詢快取,如果命中快取,則立刻返回儲存在快取中的結果。否則進入下一個階段
  • 伺服器進行SQL解析、預處理、再由優化器生成對應的執行計劃
  • MySQL根據執行計劃,呼叫儲存引擎的API來執行查詢
  • 將結果返回給客戶端,同時快取查詢結果
  • 注意:只有在8.0之前才有查詢快取,8.0之後查詢快取被去掉了

如何啟動執行計劃

explain select 投影列 FROM 表名 WHERE 條件

explain兩個變種(MySQL5.7)
explain extended : 會在explain的基礎上額外提供一些查詢優化的資訊。緊隨其後通過show warnings命令可以得到優化後的查詢語句,從而看出優化器優化了什麼。額外還有filtered列,是一個半分比的值,rows*filtered/100可以估算出將要和explain中前一個表進行連線的行數(前一個表指explain中的id值比當前表id值小的表)

EXPLAIN EXTENDED select * from actor where id=1;show WARNINGS;

explain partitions: 相比explain多了個partitions欄位,如果查詢是基於分割槽表的話,會顯示查詢將訪問的分割槽。

explain中的列

id

  • 查詢執行順序:
  • id值相同時表示從上向下執行
  • id值相同被視為一組
  • 如果是子查詢,id值會遞增,id值越高,優先順序越高
  • id為null最後執行

select_type

  • simple:表示查詢中不包含子查詢或者union
  • primary:當查詢中包含任何複雜的子部分,最外層的查詢被標記成primary
  • derived:在from的列表中包含的子查詢被標記成derived
  • subquery:在select或where列表中包含了子查詢,則子查詢被標記成subquery
  • union:兩個select查詢時前一個標記為PRIMARY,後一個標記為UNION,union出現在from從句子查詢中,外層select標記為PRIMARY,union中第一個查詢為DERIVED,第二個子查詢標記為UNION
  • unionresult:從union表獲取結果的select被標記成union result

table

  • 顯示這一行的資料是關於哪張表的
  • 當from子句中有子查詢時,table列是格式,表示當前查詢依賴id=N當查詢,於是先執行id=N當查詢
  • 當有union時,UNION RESULT的table列的值為<union1,2>,1和2表示參與union的select行id

type

  • 這是重要的列,顯示連線使用了何種型別。從最好到最差的連線型別為system>const>eq_reg>ref>range>index>ALL
  • 一般來說,得保證查詢達到range級別,最好達到ref
  • NULL:mysql能夠在優化階段分解查詢語句,在執行階段用不著再訪問表或索引。例如:在索引列中選取最小值,可以單獨查詢索引來完成,不需要在執行時訪問表
  • system:表中只有一行資料。屬於const的特例,如果物理表中就一行資料為ALL
  • const:查詢結果最多有一個匹配行,因為只有一行,所以可以被視為常量。const查詢速度非常快,因為只讀一次。一般情況下把主鍵或唯一索引作為唯一條件的查詢都是const
  • eq_ref:查詢時查詢外來鍵表全部資料。且只能查詢主鍵列或關聯列。且外來鍵表中外來鍵列中資料不能有重複資料,且這些資料都必須在主鍵表中有對應資料(主鍵表中資料可以有沒有用到的)
  • ref:比eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分字首,索引要和某個值相比較,可能會找到多個符合條件的行。
  • range:把這個列當作條件只檢索其中一個範圍。常見where從句中出現between,<,>,>=,in等。主要應用在具有索引等列中
  • index:掃描全索引就能拿到結果,一般是掃描某個二級索引,這種掃描不會從索引樹節點開始快速查詢,而是直接對二級索引等葉子節點遍歷和掃描,速度還是比較慢的,這種查詢一般為使用覆蓋索引,二級索引一般比較小,所以這種通常比ALL快一些
  • ALL:即全表掃描,掃描你的聚簇索引的所有葉子節點,通常情況下這需要增加索引來進行優化了。

possible_keys

  • 查詢條件欄位涉及到的索引,可能沒有使用。
  • explain時可能出現possible_keys有列,而key顯示NULL的情況,這種情況是因為表中資料不多,mysql認為索引對此查詢幫助不大,選擇了全表查詢。
  • 如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查where子句看是否可以創造一個適當的索引來提高查詢效能,然後用explain檢視效果

key

  • 實際使用的索引,如果為NULL,則沒有使用索引。
  • 如果想強制mysql使用或忽視possible_keys列中的索引,在查詢中使用forceindex、ignore index。

key_len

  • 表示索引中使用的位元組數,查詢中使用的索引的長度(最大可能長度),並非實際使用長度,理論上長度越短越好。key_len是根據表定義計算而得等,不是通過表內檢索出的。

ref

  • 顯示索引的哪一列被使用了,如果可能的話,是一個常量const

rows

  • 根據表統計資訊及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數,注意這個不是結果集裡的行數

filtered

  • 顯示了通過條件過濾出的行數的百分比估計值

Extra

  • Distinct:MySQL發現第1個匹配行後,停止為當前的行組合搜尋更多的行
  • Not exists:MySQL能夠對查詢進行LEFT JOIN優化,發現1個匹配LEFT JOIN標準的行後,不再為前面的行組合在該表內檢查更多的行。
  • range checked for each record (index map:#):MySQL沒有發現好的可以使用的索引,但發現如果來自前面的表的列值已知,可能部分索引可以使用。
  • Using filesort:MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。將用外部排序而不是索引排序,資料較小時從記憶體排序,否則需要在磁碟完成排序,這種情況下一般也是需要考慮使用索引來優化的。
  • Using index:從只使用索引樹中的資訊而不需要進一步搜尋讀取實際的行來檢索表中的列資訊,(使用覆蓋索引)覆蓋索引定義:mysql執行計劃explain結果裡的key有使用索引,如果select後面查詢的欄位都可以從這個索引的樹中獲取,這種情況一般可以說是用到了覆蓋索引,extr裡一般都有using index;覆蓋索引一般針對的是輔助索引,整個查詢結果只通過輔助索引就能拿到結果,不需要通過輔助索引樹找到主鍵,再通過主鍵去主鍵索引樹裡獲取其它欄位值
  • Using temporary:為了解決查詢,MySQL需要建立一個臨時表來容納結果。
  • Using where:WHERE 子句用於限制哪一個行匹配下一個表或傳送到客戶,並且查詢的列未被索引覆蓋
  • Using sort_union(...), Using union(...), Using intersect(...) 這些函式說明如何為index_merge連線型別合併索引掃描。
  • Using index for group-by:類似於訪問表的Using index方式,Using index for group-by表示MySQL發現了一個索引,可以用來查詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜尋硬碟訪問實際的表。

優化SQL就是一個抽絲剝繭找到問題本質的過程,在不斷猜測、不斷試錯的過程中,逐步接近事件的本質。你所掌握的知識點越多,可“猜測”的可能性就越多。

優化器是資料庫最核心的功能,也是最複雜的一部分。它負責將使用者提交的SQL語句根據各種判斷標準,制定出最優出最優的執行計劃,並交由執行器來最終執行。優化器演算法的好壞、能力的強弱,直接決定了語句的執行效率。筆者也使用了其他諸如MySQL,postgreSQL,SQLServer等關係型資料庫。綜合比較來說,Oracle的優化器是功能最強大的。學習SQL優化,從本質來講就是學習從優化器的角度如何看待SQL,如何制定出更優的執行計劃。當然,優化器本身是資料庫系統中最複雜的一個部分,本書會就優化器的分類、工作原理等做簡單介紹,不會深入細節。

成本是優化器(基於成本的優化器)中反映SQL語句執行代價的一個指標。優化器通過比較不同執行計劃的成本,選擇成本最小的作為最終的執行計劃。如何理解成本、成本如何計算也就成為我們學習基於成本的優化器的關鍵所在。

優化器

優化器在整個SQL語句的執行過程中充當了非常重要的角色。

執行計劃

執行計劃是SQL優化的基礎,只有在充分了解執行計劃的基礎上才能判斷語句執行是否高效。如何獲得執行計劃?怎樣讀取執行計劃?常見的執行計劃有哪些?如何幹預執行計劃?

什麼是執行計劃

資料庫執行SQL語句是按照一定順序、分步驟完成的。至於採用怎樣的順序、用什麼方法訪問資料,是由優化器來決定的。一旦優化器確定好了一個它認為最高效的執行方法,這一系列的順序、步驟就被稱為執行計劃。

SQL優化相關物件

在SQL優化的過程中,首先需要了解語句相關物件的情況。資料庫物件設計的好壞,會直接影響相關物件語句執行的效率。
因此,有時在SQL語句實在無法優化的情況下,可以考慮通過修改物件的結構來完成優化。

在做表設計時,頻繁訪問的欄位放在前面。
相關欄位型別操作時,建議使用同一型別,以免引起不必要的轉化成本。

索引

B樹索引是資料庫預設索引,也是最為常見的一種索引,通常我們所說就是B樹索引,MySQL預設建立不指定索引型別時,也是B樹索引。整個索引結構就是一個平衡樹(Balance Tree),這也就是稱為B樹索引的原因。在整個樹結構中,包含3種節點,分別是根節點,分支節點,葉子節點。有的簡單索引只有根節點和葉子節點。

點陣圖索引,雖說常見,但一般用於分析型資料庫中。

表空間:
表空間是資料庫的一種邏輯結構,它在物理上對應著一個或多個數據檔案。平常所說的表空間管理實際上指的是對錶空間所對應的資料檔案的空間管理,Oracle支援兩種管理方式:一種是字典管理(簡稱DMT),一種是本地管理(簡稱LMT)。這裡所說的管理方式是指標對extent的管理方式。extent也是資料庫的一種邏輯結構,它包含一定數量的、連續的Oracle塊,是Oracle空間分配的最小單位。針對它的管理方式是指表空間中的extent是如何被管理的(記錄extent的free、used使用情況)。

段:
資料段(即基表段),是Oracle資料庫中用於儲存基表資料段段。資料段儲存在表空間中,對應於一個或多個數據檔案(段可以來自多個檔案,但段中指定的一個區只能來自一個檔案)。每個基表段都有一個數據段(聚簇段中,兩個基表段對應一個數據段)。每當使用者建立一個基表時,系統會在使用者預設的表空間中建立一個數據段。

區:
區是磁碟空間分配的最小單位,磁碟按區劃分的,每次至少分配一個區。區儲存於段中,是資料庫儲存空間邏輯單位,是由連續的資料塊組成的。一個或多個數據塊組成一個區,一個或多個區組成一個段。當一個段中所有空間用完時,系統會自動給該段分配一個新區。段的增大是通過增加區的個數實現的。

塊:
塊是Oracle資料庫中最小的一個數據組織單位。它的大小由引數db_block_size確定,取值和os有關,一般是os物理塊的整數倍,即512Byte的倍數。塊大小在建立資料庫之前確定,資料庫建立或安裝結束後不得修改。對於系統表空間及其它預設表空間使用引數db_block_size確定塊大小,而對於其他非預設表空間使用引數blocksize確定塊大小。

索引設計

對於查詢中需要作為查詢條件的欄位,可以考慮建立索引,當然,最終還要根據效能的需要決定是否建立索引。建立索引時也要考慮維護成本,不能無序地建立索引。
構建戰略性索引的策略:
B樹索引:

  • 控制單表索引個數,儘量不要超過5個。沒有任何索引的單表也需要關注。
  • 要注意監控建立的索引中不要出現無用索引。必要時,刪除無用的索引,避免對執行計劃造成影響。
  • 如果存在外來鍵,需要在對應欄位建立索引,否則會引發死鎖並影響表連線效能。
  • 對於經常出現在WHERE子句中且過旅性比較強的欄位,特別是大表的欄位,應該建立索引。

複合索引:
當某個索引包含多個索引的列時,這種索引稱之為複合索引。當查詢多個條件為AND關係的資料時,可以使用複合索引快速定位到該資料。對複合索引的使用,應注意:

  • 過度索引:不要講所有的WHERE條件中的欄位都建立為複合索引。因為索引會造成DML操作的開銷增大,且空間上也會存在很大的浪費。
  • 複合索引中的列順序:所有的索引列都在WHERE條件中時,索引的效率和索引中的列的順序是無關的。因而,當不是所有索引的列都在WHERE條件中時,最佳的列順序要求就是能夠最大可能使用索引,換句話說,應該能夠對最多的SQL語句應用索引。當多個列的使用頻率相同時,可以遵從以下兩種截然相反的方法。
  • 如果將來的SQL語句只對部分列應用限制條件,前導列應該是具有最多唯一值的列。換句話說,就是使索引被優化器選擇的概率最大化。
    複合索引的使用建議:
  • 如果約束條件欄位比較固定,則優先考慮建立針對多欄位多普通B樹複合索引。
  • 如果單個欄位是主鍵或唯一欄位,或者可選性非常高的欄位,儘管約束條件欄位比較固定,也不一定要建立複合索引,有時可建立但欄位索引,降低複合索引開銷。
  • 在複合索引設計中,應考慮複合索引的第一個設計原理:複合索引的字首性。即在SQL語句中,只有將複合索引的第一個欄位作為約束條件,該複合索引才會啟用。
  • 在複合索引設計中,應考慮複合索引的可選性,即按可選性高低,進行復合索引欄位的排序。
  • 如果條件涉及的欄位不固定,組合比較靈活,可以考慮分別建立索引。
  • 如果是多表連線SQL語句,考慮是否可以在被驅動表的連線欄位與該表的其他約束條件欄位上建立複合索引。

分割槽索引

同分區表類似,索引頁可以建立分割槽索引,這裡有兩種分割槽索引要建立。

  • 本地(區域性)分割槽索引:對於建立的每個分割槽表,都存在一個分割槽索引。每個分割槽索引中的資料僅僅指向一個分割槽表中的資料。分割槽表和分割槽索引之間存在一對一的對映。
  • 全域性分割槽索引:索引按自己的模式分割槽。分割槽索引和分割槽表之間沒有匹配關係。

函式索引

函式索引可以是B樹索引,頁可以是點陣圖索引,它將一個函式計算的結果儲存在索引中,而不是儲存列資料本身。我們可以把基於函式的索引看作一個虛擬列上的索引,總之,所謂函式索引,是基於加工過的邏輯列建立的索引。
函式索引適用於基於基礎表中國呢一個或多個列的函式或表示式,查詢語句條件列上包含函式的情況。函式和表示式的值預先計算並存放在索引中。要使用函式索引就要分析表,啟用查詢重寫,設定函式索引,會減慢DML速度,因為需要先求函式值或表示式。

https://blog.csdn.net/horses/article/details/85059678
http://www.weijingbiji.com/2124/

正規化與逆正規化

基本結構設計,應儘量滿足正規化設計。但是完全滿足正規化設計的資料庫往往不是最好的設計。
1.正規化設計:
正規化設計主要包括三個。在資料庫設計中,為了更好地應用三個正規化,必須通俗地理解三個正規化(通俗理解並不是最科學、最準確的)

  • 第一正規化(1NF):對屬性的原子性約束,要求屬性具有原子性,不可再分解。
  • 第二正規化(2NF):對記錄的唯一性約束,要求記錄有唯一標識,即實體的唯一性。
  • 第三正規化(3NF):對欄位的冗餘性約束,即任何欄位不能由其他欄位派生,要求欄位沒有冗餘。
    沒有冗餘的資料庫可以做到,但其未必是最好的資料庫。有時為了提高執行效率,就必須降低正規化標準,適當保留冗餘資料。具體做法是:在概念資料模型設計時遵守第三正規化,在物理資料模型設計時降低正規化標準的。降低正規化就是增加欄位,允許冗餘。
    2.逆正規化設計:
    逆正規化的好處是降低連線操作的需求、降低外來鍵和索引的數目,以及減少表的數目,相應帶來的問題是可能出現數據的完整性問題。逆正規化設計可以加快查詢速度,但會降低修改速度。因此決定做逆正規化時,一定要權衡利弊,仔細分析應用的資料存取需求和實際的效能特點。好的索引設計通常能夠解決效能問題,而不必採用逆正規化設計。

其他設計問題

1.表與實體的關係
表與實體的關係可以是一對一、一對多、多對一的關係。一般情況下,它們是一對一的關係,即一條記錄對應且只對應一個實體。在特殊情況下,它們可能是一對多或多對一的關係。

2.多對多的關係
若兩個實體之間存在多對多的關係,則應消除這種關係。消除的辦法是,在兩者之間增加第三個實體,即將原來兩個實體的屬性合理地分配到三個實體中。這樣原來一個多對多的關係就變成了兩個一對多的關係。

3.業務主鍵與自然主鍵
業務主鍵是指表中的一個或幾個欄位組合在一起構成的主鍵。自然主鍵是指通過資料庫的方式定義的主鍵,只作為記錄的唯一標識,沒有任何業務含義。我們不需要從資料建模和資料庫設計的角度去關注業務主鍵和自然主鍵,從效能的角度探究人造鍵的優點才是明智之舉。毫無疑問,自然主鍵通常會帶來更好的效能。

編碼規範

1.單條SQL不宜超過100行。
2.當一條SQL或PLSQL語句中涉及多個表時,始終使用別名來限定欄位名。別名要避免使用毫無意義的代號,方便維護者閱讀。
3.儲存過程、函式、觸發器、程式塊中定義的變數和輸入/輸出引數在命名上要有所區分。
4.避免使用SELECT *語句,應給出欄位列表,同樣,INSERT語句也必須給出欄位列表,避免由於表結構的更改導致語句不可執行。
5.要從表中的同一筆記錄中獲取記錄的欄位值,必須使用同一SQL語句,不允許使用多條SQL語句,這樣可以降低與資料庫的互動。
6.儲存過程中變數的定義應放在AS和BEGIN關鍵字之間,不允許在程式碼中隨意定義變數。相同功能模組的變數放在一起,與其他模組的變數之間應用以空行分隔,這樣可以增加程式碼的可讀性。
7.IN、OUT引數按類別分開書寫,不允許交叉。