MySQL相關知識點(持續更新)

一、索引

1.1 爲什麼要使用索引?

這個可以類比查新華字典,假如字典前面沒有“按偏旁部首”、“按拼音”等查詢,你要是去查一個字就只能一個一個去字典裏面翻了,效率很低。但是按照“偏旁部首”去查就快多了,不用盲目地去一個個翻找。“按偏旁部首”、“按拼音”其實就是字典爲自己建立的索引。

同理,數據庫爲什麼也要建立索引?假如不建立索引,你要查一條記錄就只能一條一條去查找,效率低下。有了索引,你就可以不斷的縮小查找範圍來篩選出最終想要的結果,大大提高MySQL的檢索速度。

實際上,索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄。也就是說你在新增/更新數據庫的數據時同時要維護這條記錄的索引,這會降低新增/更新表的速度。那你肯定會有疑問:這多維護了一個索引表也是很佔用中資源的,爲啥還要用索引?實際上這和數據庫的讀寫比例是相關的,一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最多的,也是最容易出問題的,還是一些複雜的查詢操作,因此對查詢語句的優化顯然是重中之重,那麼索引就有了自己的舞臺。一旦爲表創建了索引,以後的查詢最好先查索引,再根據索引定位的結果去找數據。

索引本質:通過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查找方式來鎖定數據。

1.2 索引越多越好?

當然不是,若索引太多,會降低寫入的效率,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點。

1.3 索引什麼時候創建更好?

既然索引“性價比”這麼高,這麼重要,那什麼時候創建它合適呢?無外乎兩種:事前添加 和 事後添加。

事前添加:如果知道數據的使用,從一開始就在需要處添加索引。這種是比較推薦的做法,事前根據業務需要添加好索引,這時數據庫中也沒有數據,因此建立索引會容易一些。

事後添加:事後添加因爲已經有大量數據,創建索引速度會很慢,建立索引就更加麻煩;另外不容易觀察索引效果,需要監控大量的SQL語句。

因此建議事前添加好索引,且注意索引數量(非必要的索引不添加),保持讀寫效率的平衡。

1.4  索引原理

1.4.1 基於B+樹

索引的設計從微機原理的角度,目的就是將IO操作的次數減少到最低,IO操作是最耗時間的,只要控制它的次數就能最好的提高查詢效率。所以每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級,就是我們索引設計的目標。

能夠實現上面目的就是B+樹,B+樹的詳細可以參考:https://blog.csdn.net/weixin_41231928/article/details/106652325,看下面的一個B+樹圖:

一個磁盤塊或者叫做一個block塊,這是操作系統一次IO往內存中讀的內容,一個塊對應四個扇。如磁盤塊1包含數據項16和36,包含指針P1、P2、P3,P1表示小於16的磁盤塊,P2表示在16和36之間的磁盤塊,P3表示大於36的磁盤塊。最後一層是葉子節點,保存真實數據。(B+樹葉子層是有指針連接的,這裏沒有畫)

查詢過程:

  • (1)若要查找20,首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定20在16和36之間,鎖定磁盤塊1的P2指針;
  • (2)通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,20在27和26之間,鎖定磁盤塊3的P2指針
  • (3)通過指針加載磁盤塊8到內存,發生第三次IO,內存中找到20,結束查詢,總計三次IO。

通過上面的查詢過程我們有以下總結:

  1. 這裏順便在解釋下索引的必要性,若是不用索引,每個數據項都要發生一次IO,數據量達到百萬千萬級別時,會大大增加查詢成本。
  2. 索引字段要儘量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h或者說層級,所以要降低樹的層級,在總數據量一定時,那就需要每個磁盤存儲的數據儘可能的多,也就需要每個數據單元儘可能的小,這樣每個磁盤塊就能儘可能多裝數據,樹就會變得越“矮胖”,層級就會更低,IO的次數就會更低。

1.4.2 基於Hash

除了B+樹實現的索引外,還有基於Hash實現的索引。

Hash實現的索引利用了哈希表hash table(key,value,它就是把Key通過一個固定的算法函數既所謂的哈希函數轉換成一個整型數字,然後就將該數字對數組長度進行取餘,取餘結果就當作數組的下標,將value存儲在以該數字爲下標的數組空間裏。而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換爲對應的數組下標,並定位到該空間獲取value,如此一來,就可以充分利用到數組的定位性能進行數據定位。

Hash 索引結構的特殊性,其檢索效率非常高,索引的檢索可以一次定位,不像B-Tree 索引需要從根節點到枝節點,最後才能訪問到頁節點這樣多次的IO訪問,所以 Hash 索引的查詢效率要遠高於 B-Tree 索引。可能很多人又有疑問了,既然 Hash 索引的效率要比 B-Tree 高很多,爲什麼大家不都用 Hash 索引而還要使用 B-Tree 索引呢?任何事物都是有兩面性的,Hash 索引也一樣,雖然 Hash 索引效率高,但是 Hash 索引本身由於其特殊性也帶來了很多限制和弊端,主要有以下這些:

  1. Hash 索引僅僅能滿足”=”,”IN”和”<=>”查詢,不能使用範圍查詢。由於 Hash 索引比較的是進行 Hash 運算之後的 Hash 值,所以它只能用於等值的過濾,不能用於基於範圍的過濾,因爲經過相應的 Hash 算法處理之後的 Hash 值的大小關係,並不能保證和Hash運算前完全一樣。
  2. Hash 索引無法被用來避免數據的排序操作。由於 Hash 索引中存放的是經過 Hash 計算之後的 Hash 值,而且Hash值的大小關係並不一定和 Hash 運算前的鍵值完全一樣,所以數據庫無法利用索引的數據來避免任何排序運算;
  3. Hash 索引不能利用部分索引鍵查詢。對於組合索引,Hash 索引在計算 Hash 值的時候是組合索引鍵合併後再一起計算 Hash 值,而不是單獨計算 Hash 值,所以通過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也無法被利用。
  4. Hash 索引在任何時候都不能避免表掃描。Hash 索引是將索引鍵通過 Hash 運算之後,將 Hash運算結果的 Hash 值和所對應的行指針信息存放於一個 Hash 表中,由於不同索引鍵存在相同 Hash 值,所以即使取滿足某個 Hash 鍵值的數據的記錄條數,也無法從 Hash 索引中直接完成查詢,還是要通過訪問表中的實際數據進行相應的比較,並得到相應的結果。
  5. Hash 索引遇到大量Hash值相等的情況後性能並不一定就會比B-Tree索引高。對於選擇性比較低的索引鍵,如果創建 Hash 索引,那麼將會存在大量記錄指針信息存於同一個 Hash 值相關聯。這樣要定位某一條記錄時就會非常麻煩,會浪費多次表數據的訪問,而造成整體性能低下

1.5  索引分類

上面說了索引的作用、原理,下面說下分類,基本的可以分爲:

普通索引:僅加速查詢

唯一索引:加速查詢 + 列值唯一(可以有null)

主鍵索引:加速查詢 + 列值唯一(不可以有null)+ 表中只有一個

組合索引:多列值組成一個索引,專門用於組合搜索,其效率大於索引合併

全文索引:對文本的內容進行分詞,進行搜索


除了這些基礎的索引,因爲叫法不同,研究背景不同,還有下面一些索引,這些概念容易混淆,做好理解:

主索引:主索引就是主鍵索引

輔助索引:就是根據業務需要,自己設置的普通的非主鍵的索引。這個在Myisam裏面區別不大,但是在Innodb的時候差別很大

聚集索引: 聚集索引就是按照每張表的主鍵構造一顆B+樹,同時葉子節點中存放的即爲整張表的記錄數據。一個表只能有1個聚簇索引,因爲表數據存儲的物理位置是唯一的。聚簇索引的value存的就是真實的數據,不是數據的地址。主索引樹裏面包含了真實的數據。key是主鍵值,value值就是data,key值按照B+樹的規則分散排布的葉子節點,聚簇索引的順序就是數據的物理存儲順序,所以一個表最多隻能有一個聚簇索引,因爲物理存儲只能有一個順序。正因爲一個表最多隻能有一個聚簇索引,所以它顯得更爲珍貴,一個表設置什麼爲聚簇索引對性能很關鍵。nnodb的主索引採用的是聚簇索引。

非聚集索引:索引和表數據是分離的,索引的value值指向物理的存儲地址。Myisam主索引和輔助索引都採用的是非聚簇索引。

覆蓋索引: SQL只需要通過索引就可以返回查詢所需要的數據,而不必通過二級索引查到主鍵之後再去查詢數據,一個索引中包含所有需要查詢字段的值。如果一個索引包含(或覆蓋)所有需要查詢的字段的值,稱爲‘覆蓋索引’。即只需掃描索引而無須回表。使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作

最左匹配特性:主要針對組合索引,從數據塊的左邊開始匹配,再匹配右邊的。比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因爲建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。

1.6 各種索引的應用場景

舉個例子來說,比如你在爲某商場做一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麼這個 會員編號,作爲主鍵,使用 PRIMARY
會員姓名 如果要建索引的話,那麼就是普通的 INDEX
會員身份證號碼 如果要建索引的話,那麼可以選擇 UNIQUE (唯一的,不允許重複)

#除此之外還有全文索引,即FULLTEXT
會員備註信息 , 如果需要建索引的話,可以選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,如果就一兩行字的,普通的 INDEX 也可以。
但其實對於全文搜索,我們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來做全文搜索。

#其他的如空間索引SPATIAL,瞭解即可,幾乎不用

1.7  索引的創建/刪除

創建索引時,你需要確保該索引是應用在 SQL 查詢語句的條件(一般作爲 WHERE 子句的條件)。 

 主鍵索引:
創建的時候添加:  添加索引的時候要注意,給字段裏面數據大小比較小的字段添加,給字段裏面的數據區分度高的字段添加.
聚集索引的添加方式
創建的是添加
Create table t1(
Id int primary key,
)
Create table t1(
Id int,
Primary key(id)
)

表創建完了之後添加
Alter table 表名 add primary key(id)
刪除主鍵索引:
Alter table 表名 drop primary key;

唯一索引:
Create table t1(
Id int unique,
)

Create table t1(
Id int,
Unique key uni_name (id)
)

表創建好之後添加唯一索引:
alter table s1 add unique key  u_name(id);
刪除:
Alter table s1 drop index u_name;

普通索引:
創建:
Create table t1(
Id int,
Index index_name(id)
)

表創建好之後添加普通索引:
Alter table s1 add index index_name(id);
Create index index_name on s1(id);

刪除:
Alter table s1 drop index u_name;
DROP INDEX 索引名 ON 表名字;

創建索引小結:

  1. 一定是爲搜索條件的字段創建索引,比如select * from s1 where id = 333;就需要爲id加上索引
  2. 在表中已經有大量數據的情況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快
    比如create index idx on s1(id);會掃描表中所有的數據,然後以id爲數據項,創建索引結構,存放於硬盤的表中。
    建完以後,再查詢就會很快了。因此建議建表時就建立索引。
  3. 儘量選擇區分度高的列作爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0

  4. 最左前綴匹配原則(詳見第八小節),非常重要的原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配(指的是範圍大了,有索引速度也慢),比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。索引是有個最左匹配的原則的,所以建聯合索引的時候,將區分度高的放在最左邊,依次排下來。

 

ps:創建的索引有沒有被使用到?或者說怎麼纔可以知道這條語句運行很慢的原因?

MySQL提供了explain命令來查看語句的執行計劃,MySQL在執行某個語句之前,會將該語句過一遍查詢優化器,之後會拿到對語句的分析,也就是執行計劃,其中包含了許多信息. 可以通過其中和索引有關的信息來分析是否命中了索引,例如possilbe_key,key,key_len等字段,分別說明了此語句可能會使用的索引,實際使用的索引以及使用的索引長度。

 

那麼在哪些情況下會發生針對該列創建了索引但是在查詢的時候並沒有使用呢?

  1. 使用不等於查詢
  2. 列參與了數學運算或者函數
  3. 在字符串like時左邊是通配符.類似於'%aaa'
  4. 當mysql分析全表掃描比使用索引快的時候不使用索引
  5. 當使用聯合索引,前面一個條件爲範圍查詢,後面的即使符合最左前綴原則,也無法使用索引

 

 二、事務

2.1 概念

MySQL 事務主要用於處理操作量大,複雜度高的數據。比如說,在人員管理系統中,你刪除一個人員,你既需要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操作語句就構成一個事務!

  • 在 MySQL 中只有使用了 Innodb 數據庫引擎的數據庫或表才支持事務。
  • 事務處理可以用來維護數據庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行。
  • 事務用來管理 insert,update,delete 語句

一般來說,事務是必須滿足4個條件(ACID):。

  • 原子性:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  • 一致性:在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續數據庫可以自發性地完成預定的工作。

  • 隔離性:數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。

  • 持久性:事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。 

ps: 在 MySQL 命令行的默認設置下,事務都是自動提交的,即執行 SQL 語句後就會馬上執行 COMMIT 操作。因此要顯式地開啓一個事務務須使用命令 BEGIN 或 START TRANSACTION,或者執行命令 SET AUTOCOMMIT=0,用來禁止使用當前會話的自動提交。

2.2  事務控制語句

  • BEGIN 或 START TRANSACTION 顯式地開啓一個事務;

  • COMMIT 也可以使用 COMMIT WORK,不過二者是等價的。COMMIT 會提交事務,並使已對數據庫進行的所有修改成爲永久性的;

  • ROLLBACK 也可以使用 ROLLBACK WORK,不過二者是等價的。回滾會結束用戶的事務,並撤銷正在進行的所有未提交的修改;

  • SAVEPOINT identifier,SAVEPOINT 允許在事務中創建一個保存點,一個事務中可以有多個 SAVEPOINT;

  • RELEASE SAVEPOINT identifier 刪除一個事務的保存點,當沒有指定的保存點時,執行該語句會拋出一個異常;

  • ROLLBACK TO identifier 把事務回滾到標記點;

  • SET TRANSACTION 用來設置事務的隔離級別。InnoDB 存儲引擎提供事務的隔離級別有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

2.3  MYSQL 事務處理主要有兩種方法

1、用 BEGIN, ROLLBACK, COMMIT來實現

  • BEGIN 開始一個事務
  • ROLLBACK 事務回滾
  • COMMIT 事務確認

2、直接用 SET 來改變 MySQL 的自動提交模式:

  • SET AUTOCOMMIT=0 禁止自動提交
  • SET AUTOCOMMIT=1 開啓自動提交

2.4  事務的併發問題

  1. 髒讀:事務A讀取了事務B更新的數據,然後B回滾操作,那麼A讀取到的數據是髒數據
  2. 不可重複讀:事務 A 多次讀取同一數據,事務 B 在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
  3. 幻讀:系統管理員A將數據庫中所有學生的成績從具體分數改爲ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。

小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表。

2.5  事務隔離級別

MySQL默認的事務隔離級別爲repeatable-read(可重複讀),不能解決幻讀的問題。

下面開始分別介紹幾種隔離級別,開始盜圖:

2.5.1 讀未提交

(1)打開一個客戶端A,並設置當前事務模式爲read uncommitted(未提交讀),查詢表account的初始值:

(2)在客戶端A的事務提交之前,打開另一個客戶端B,更新表account:

(3)這時,雖然客戶端B的事務還沒提交,但是客戶端A就可以查詢到B已經更新的數據:

(4)一旦客戶端B的事務因爲某種原因回滾,所有的操作都將會被撤銷,那客戶端A查詢到的數據其實就是髒數據:

(5)在客戶端A執行更新語句update account set balance = balance - 50 where id =1,lilei的balance沒有變成350,居然是400,是不是很奇怪,數據不一致啊,如果你這麼想就太天真 了,在應用程序中,我們會用400-50=350,並不知道其他會話回滾了,要想解決這個問題可以採用讀已提交的隔離級別

  2.5.2  讀已提交(不可重複讀)

就是隻能讀取已經提交的,未提交的讀不到。

(1)打開一個客戶端A,並設置當前事務模式爲read committed(未提交讀),查詢表account的所有記錄:

(2)在客戶端A的事務提交之前,打開另一個客戶端B,更新表account:

(3)這時,客戶端B的事務還沒提交,客戶端A不能查詢到B已經更新但未提交的數據,解決了髒讀問題

(4)客戶端B的事務提交

(5)客戶端A執行與上一步相同的查詢,結果與上一步不一致,即產生了不可重複讀的問題

 2.5.3  可重複讀

(1)打開一個客戶端A,並設置當前事務模式爲repeatable read,查詢表account的所有記錄

(2)在客戶端A的事務提交之前,打開另一個客戶端B,更新表account並提交

(3)在客戶端A查詢表account的所有記錄,與步驟(1)查詢結果一致,沒有出現不可重複讀的問題

(4)在客戶端A,接着執行update balance = balance - 50 where id = 1,balance沒有變成400-50=350,lilei的balance值用的是步驟(2)中的350來算的,所以是300,數據的一致性倒是沒有被破壞。可重複讀的隔離級別下使用了MVCC機制,select操作不會更新版本號,是快照讀(歷史版本);insert、update和delete會更新版本號,是當前讀(當前版本)。

2.5.4  串行化

(1)打開一個客戶端A,並設置當前事務模式爲serializable,查詢表account的初始值:

(2)打開一個客戶端B,並設置當前事務模式爲serializable,插入一條記錄報錯,表被鎖了插入失敗,mysql中事務隔離級別爲serializable時會鎖表,因此不會出現幻讀的情況,這種隔離級別併發性極低,開發中很少會用到。

 

     事務隔離級別小結:

  1. 事務隔離級別爲讀提交時,寫數據只會鎖住相應的行
  2. 事務隔離級別爲可重複讀時,如果檢索條件有索引(包括主鍵索引)的時候,默認加鎖方式是next-key 鎖;如果檢索條件沒有索引,更新數據時會鎖住整張表。一個間隙被事務加了鎖,其他事務是不能在這個間隙插入記錄的,這樣可以防止幻讀。
  3. 事務隔離級別爲串行化時,讀寫數據都會鎖住整張表
  4. 隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也越大。

三、MySQL中的鎖

3.1 鎖的分類

MySQL用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖,這些鎖都可以歸類爲悲觀鎖(Pessimistic Lock)。

表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。 
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。 
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。 


僅從鎖的角度來說:表級鎖更適合於以查詢爲主,只有少量按索引條件更新數據的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少量不同數據,同時又有 併發查詢的應用,如一些在線事務處理(OLTP)系統。

3.2 存儲引擎和鎖

不同的存儲引擎支持不同的鎖機制。比如,MyISAM和MEMORY存儲引擎採用的是表級鎖(table-level locking);BDB存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認情況下是採用行級鎖。

3.2.1 InnoDB鎖

 在mysql 的 InnoDB引擎支持行鎖,分爲兩種:

共享鎖又稱:讀鎖。當一個事務對某幾行上讀鎖時,允許其他事務對這幾行進行讀操作,但不允許其進行寫操作,也不允許其他事務給這幾行上排它鎖,但允許上讀鎖。

排它鎖又稱:寫鎖。當一個事務對某幾個上寫鎖時,不允許其他事務寫,但允許讀。更不允許其他事務給這幾行上任何鎖。包括寫鎖。

 語法

上共享鎖的寫法:lock in share mode

例如: select  *  from 表 where  條件  lock in share mode;

上排它鎖的寫法:for update

例如:select *  from 表  where 條件 for update; 

 

insert ,delete , update在事務中都會自動默認加上排它鎖。

 

四、存儲引擎 

4.1  什麼是存儲引擎?

MySQL中的數據用各種不同的技術存儲在文件(或者內存)中。這些技術中的每一種技術都使用不同的存儲機制、索引技巧、鎖定水平並且最終提供廣泛的不同的功能和能力。通過選擇不同的技術,你能夠獲得額外的速度或者功能,從而改善你的應用的整體功能。

例如,如果你在研究大量的臨時數據,你也許需要使用內存MySQL存儲引擎。內存存儲引擎能夠在內存中存儲所有的表格數據。又或者,你也許需要一個支持事務處理的數據庫(以確保事務處理不成功時數據的回退能力。

這些不同的技術以及配套的相關功能在 MySQL中被稱作存儲引擎(也稱作表類型)。

MySQL默認配置了許多不同的存儲引擎,可以預先設置或者在MySQL服務器中啓用。你可以選擇適用於服務器、數據庫和表格的存儲引擎,以便在選擇如何存儲你的信息、如何檢索這些信息以及你需要你的數據結合什麼性能和功能的時候爲你提供最大的靈活性。

4.2  存儲引擎分類

4.2.1  MyISAM

它不支持事務,也不支持外鍵,但是訪問速度快,對事務完整性沒有要求或者以SELECT、INSERT爲主的應用基本都可以使用這個引擎來創建表。

4.2.2  InnoDB

InnoDB是一個健壯的事務型存儲引擎,這種存儲引擎已經被很多互聯網公司使用,爲用戶操作非常大的數據存儲提供了一個強大的解決方案。我的電腦上安裝的MySQL 5.6.13版,InnoDB就是作爲默認的存儲引擎。InnoDB還引入了行級鎖定和外鍵約束,在以下場合下,使用InnoDB是最理想的選擇:

1.更新密集的表。InnoDB存儲引擎特別適合處理多重併發的更新請求。
2.事務。InnoDB存儲引擎是支持事務的標準MySQL存儲引擎。
3.自動災難恢復。與其它存儲引擎不同,InnoDB表能夠自動從災難中恢復。
4.外鍵約束。MySQL支持外鍵的存儲引擎只有InnoDB。
5.支持自動增加列AUTO_INCREMENT屬性。

一般來說,如果需要事務支持,並且有較高的併發讀取頻率,InnoDB是不錯的選擇。

4.2.3  MEMORY

使用MySQL Memory存儲引擎的出發點是速度。爲得到最快的響應時間,採用的邏輯存儲介質是系統內存。

雖然在內存中存儲表數據確實會提供很高的性能,但當mysqld守護進程崩潰時,所有的Memory數據都會丟失。獲得速度的同時也帶來了一些缺陷。它要求存儲在Memory數據表裏的數據使用的是長度不變的格式,這意味着不能使用BLOB和TEXT這樣的長度可變的數據類型,VARCHAR是一種長度可變的類型,但因爲它在MySQL內部當做長度固定不變的CHAR類型,所以可以使用。

一般在以下幾種情況下使用Memory存儲引擎:

  1. 目標數據較小,而且被非常頻繁地訪問。在內存中存放數據,所以會造成內存的使用,可以通過參數max_heap_table_size控制Memory表的大小,設置此參數,就可以限制Memory表的最大大小。
  2. 如果數據是臨時的,而且要求必須立即可用,那麼就可以存放在內存表中。
  3. 存儲在Memory表中的數據如果突然丟失,不會對應用服務產生實質的負面影響。

Memory同時支持散列索引和B樹索引。B樹索引的優於散列索引的是,可以使用部分查詢和通配查詢,也可以使用<、>和>=等操作符方便數據挖掘。散列索引進行“相等比較”非常快,但是對“範圍比較”的速度就慢多了,因此散列索引值適合使用在=和<>的操作符中,不適合在<或>操作符中,也同樣不適合用在order by子句中。

4.2.4  MERGE

MERGE存儲引擎是一組MyISAM表的組合,這些MyISAM表結構必須完全相同,儘管其使用不如其它引擎突出,但是在某些情況下非常有用。說白了,Merge表就是幾個相同MyISAM表的聚合器;Merge表中並沒有數據,對Merge類型的表可以進行查詢、更新、刪除操作,這些操作實際上是對內部的MyISAM表進行操作。Merge存儲引擎的使用場景。

對於服務器日誌這種信息,一般常用的存儲策略是將數據分成很多表,每個名稱與特定的時間端相關。例如:可以用12個相同的表來存儲服務器日誌數據,每個表用對應各個月份的名字來命名。當有必要基於所有12個日誌表的數據來生成報表,這意味着需要編寫並更新多表查詢,以反映這些表中的信息。與其編寫這些可能出現錯誤的查詢,不如將這些表合併起來使用一條查詢,之後再刪除Merge表,而不影響原來的數據,刪除Merge表只是刪除Merge表的定義,對內部的表沒有任何影響。

 4.2.5 ARCHIVE

Archive是歸檔的意思,在歸檔之後很多的高級功能就不再支持了,僅僅支持最基本的插入和查詢兩種功能。在MySQL 5.5版以前,Archive是不支持索引,但是在MySQL 5.5以後的版本中就開始支持索引了。Archive擁有很好的壓縮機制,它使用zlib壓縮庫,在記錄被請求時會實時壓縮,所以它經常被用來當做倉庫使用。

場景:由於高壓縮和快速插入的特點Archive非常適合作爲日誌表的存儲引擎,但是前提是不經常對該表進行查詢操作。
 

五、表結構設計

 5.1 爲什麼要給表加上主鍵?

  1. 一個沒加主鍵的表,它的數據無序的放置在磁盤存儲器上,一行一行的排列的很整齊.
  2. 一個加了主鍵的表,並不能被稱之爲「表」。如果給表上了主鍵,那麼表在磁盤上的存儲結構就由整齊排列的結構轉變成了樹狀結構,並且是「平衡樹」結構,換句話說,就是整個表就變成了一個索引。沒錯,再說一遍,整個表變成了一個索引,也就是所謂的「聚集索引」。 這就是爲什麼一個表只能有一個主鍵,一個表只能有一個「聚集索引」,因爲主鍵的作用就是把「表」的數據格式轉換成「索引(平衡樹)」的格式放置。
  3. 業務需要:表中每一行都應該有可以唯一標識自己的一列(或一組列),唯一標識業務。

 5.2 主鍵使用自增ID還是UUID

自增

優點:

  1. 字段長度較uuid小很多,可以是bigint甚至是int類型,這對檢索的性能會有所影響。我們平時數據庫一般用的都是innodb引擎的表,這種表格檢索數據的時候,哪怕走索引,也是先根據索引找到主鍵,然後由主鍵找到這條記錄。所以主鍵的長度短的話,讀性能是會好一點的
  2. 因爲在InnoDB存儲引擎中,主鍵索引是作爲聚簇索引存在的,也就是說,主鍵索引的B+樹葉子節點上存儲了主鍵索引以及全部的數據(按照順序),如果主鍵索引是自增ID,那麼只需要不斷向後排列即可,如果是UUID,由於到來的ID與原來的大小不確定,會造成非常多的數據插入,數據移動,然後導致產生很多的內存碎片,進而造成插入性能的下降

缺點:

  1. 很容易被別人知曉業務量,受網絡爬蟲侵害
  2. 高併發的情況下,競爭自增鎖會降低數據庫的吞吐能力
  3. 數據遷移的時候,特別是發生表格合併會很麻煩

 

UUID

優點:

  1. 唯一的guid,不會衝突
  2. 可以在應用層生成,提高數據庫吞吐能力

缺點:

  1. 與自增相比,最大的缺陷就是隨機io。這一點又要談到我們的innodb了,因爲這個默認引擎,表中數據是按照主鍵順序存放的。也就是說,如果發生了隨機io,就會頻繁地移動磁盤塊。當數據量大的時候,寫的短板將非常明顯。
  2. 讀取出來的數據也是沒有規律的,通常需要order by,其實也很消耗數據庫資源

綜合起來,一般推薦使用自增ID,不要使用UUID。

 5.3 超大分頁

  5.3.1 傳統的limit

在MySQL中limit可以實現快速分頁:

select * from product limit 866613, 20;   # 37.44秒

1)limit語句的查詢時間與起始記錄的位置成正比
2)mysql的limit語句是很方便,但是對記錄很多的表並不適合直接使用。

limit可以實現快速分頁,但是如果數據到了幾百萬時limit必須優化纔能有效的合理的實現分頁了,否則可能會很慢。 有下面幾個解決方案:

 5.3.2  方案一:覆蓋索引

利用表的覆蓋索引來加速分頁查詢,我們都知道,利用了索引查詢的語句中如果只包含了那個索引列(覆蓋索引),那麼這種情況會查詢很快。

因爲利用索引查找有優化算法,且數據就在查詢索引上面,不用再去找相關的數據地址了,這樣節省了很多時間。另外Mysql中也有相關的索引緩存,在併發高的時候利用緩存就效果更好了。

在我們的例子中,我們知道id字段是主鍵,自然就包含了默認的主鍵索引。現在讓我們看看利用覆蓋索引的查詢效果如何:

這次我們之間查詢最後一頁的數據(利用覆蓋索引,只包含id列),如下:

select id from product limit 866613, 20  # 0.2秒

相對於查詢了所有列的37.44秒,提升了大概100多倍的速度。

5.3.3  方案二:用id優化

先找到上次分頁的最大ID,然後利用id上的索引來查詢,類似於select * from user where id>1000000 limit 100.
這樣的效率非常快,因爲主鍵上是有索引的,但是這樣有個缺點,就是ID必須是連續的,並且查詢不能有where語句,因爲where語句會造成過濾數據.

5.3.4 方案三:利用緩存

解決超大分頁,還可以靠緩存,可預測性的提前查到內容,緩存至redis等k-V數據庫中,直接返回即可。

5.4  三個範式

設計關係數據庫時,遵從不同的規範要求,設計出合理的關係型數據庫,這些不同的規範要求被稱爲不同的範式,各種範式呈遞次規範,越高的範式數據庫冗餘越小。

目前關係數據庫有六種範式:第一範式(1NF)、第二範式(2NF)、第三範式(3NF)、巴斯-科德範式(BCNF)、第四範式(4NF)和第五範式(5NF,又稱完美範式)。滿足最低要求的範式是第一範式(1NF)。在第一範式的基礎上進一步滿足更多規範要求的稱爲第二範式(2NF),其餘範式以次類推。一般說來,數據庫只需滿足第三範式(3NF)就行了。所以這裏就只記錄三範式相關的知識。

1NF:字段不可分; 
2NF:有主鍵,非主鍵字段依賴主鍵; 
3NF:非主鍵字段不能相互依賴; 

解釋: 
1NF:原子性 字段不可再分,否則就不是關係數據庫; 
2NF:唯一性 一個表只說明一個事物; 
3NF:每列都與主鍵有直接關係,不存在傳遞依賴; 

第一範式(1NF)

  即表的列的具有原子性,不可再分解,即列的信息,不能分解, 只要數據庫是關係型數據庫(mysql/oracle/db2/informix/sysbase/sql server),就自動的滿足1NF。數據庫表的每一列都是不可分割的原子數據項,而不能是集合,數組,記錄等非原子數據項。如果實體中的某個屬性有多個值時,必須拆分爲不同的屬性 。通俗理解即一個字段只存儲一項信息。

第二範式(2NF)

  第二範式(2NF)是在第一範式(1NF)的基礎上建立起來的,即滿足第二範式(2NF)必須先滿足第一範式(1NF)。第二範式(2NF)要求數據庫表中的每個實例或行必須可以被惟一地區分。爲實現區分通常需要我們設計一個主鍵來實現(這裏的主鍵不包含業務邏輯)。

即滿足第一範式前提,當存在多個主鍵的時候,纔會發生不符合第二範式的情況。比如有兩個主鍵,不能存在這樣的屬性,它只依賴於其中一個主鍵,這就是不符合第二範式。通俗理解是任意一個字段都只依賴表中的同一個字段。(涉及到表的拆分)

第三範式(3NF)

  滿足第三範式(3NF)必須先滿足第二範式(2NF)。簡而言之,第三範式(3NF)要求一個數據庫表中不包含已在其它表中已包含的非主鍵字段。我們爲了滿足第三範式往往會把一張表分成多張表。

即滿足第二範式前提,如果某一屬性依賴於其他非主鍵屬性,而其他非主鍵屬性又依賴於主鍵,那麼這個屬性就是間接依賴於主鍵,這被稱作傳遞依賴於主屬性。 通俗解釋就是一張表最多隻存兩層同類型信息。

但是有些時候一昧的追求範式減少冗餘,反而會降低數據讀寫的效率,這個時候就要反範式,利用空間來換時間。

 

六、慢查詢

6.1 什麼是慢查詢?

 MySQL的慢查詢,全名是慢查詢日誌,是MySQL提供的一種日誌記錄,用來記錄在MySQL中響應時間超過閥值的語句。

具體環境中,運行時間超過long_query_time值的SQL語句,則會被記錄到慢查詢日誌中。long_query_time的默認值爲10,意思是記錄運行10秒以上的語句。慢查詢日誌支持將日誌記錄寫入文件和數據庫表。

一條語句執行10秒以上,肯定影響用戶體驗啊,慢查詢記錄這些耗時的sql,就給優化響應時間提供了方向。

6.2 怎麼開啓慢查詢?

默認情況下,MySQL數據庫並不啓動慢查詢日誌,需要手動來設置這個參數。

當然,如果不是調優需要的話,一般不建議啓動該參數,因爲開啓慢查詢日誌會或多或少帶來一定的性能影響。

默認情況slow_query_log的值是OFF,表示慢查詢日誌是禁用的,可以通過設置slow_query_log的值來開啓:

mysql> show variables  like '%slow_query_log%';
 +---------------------+-----------------------------------------------+
 | Variable_name       | Value                                         |
 +---------------------+-----------------------------------------------+
 | slow_query_log      | OFF                                           |
 | slow_query_log_file | /home/WDPM/MysqlData/mysql/DB-Server-slow.log |
 +---------------------+-----------------------------------------------+
 2 rows in set (0.00 sec)
 
mysql> set global slow_query_log=1;
 Query OK, 0 rows affected (0.09 sec)

使用 set slow_query_log=1 開啓了慢查詢日誌只對當前數據庫生效,MySQL重啓後則會失效。如果要永久生效,就必須修改配置文件(其它系統變量也是如此)。my.cnf要增加或修改參數,如下所示:

slow_query_log = 1
slow_query_log_file = /tmp/mysql_slow.log

最後重啓MySQL服務器。slow_query_log_file指定了慢查詢日誌的存放路徑,缺省情況是host_name-slow.log文件.

6.3 常見參數

除了slow_query_log_file、slow_query_log還有很多和慢查詢相關的配置參數。

6.3.1 long_query_time

默認情況下long_query_time的值爲10秒,可以使用命令修改,也可以在my.cnf參數裏面修改。關於運行時間正好等於long_query_time的情況,並不會被記錄下來。也就是說,在mysql源碼裏是判斷大於long_query_time,而非大於等於。

6.3.2 log_output

log_output參數指定日誌的存儲方式。log_output='FILE', 表示將日誌存入文件,默認值也是'FILE'。log_output='TABLE'表示將日誌存入數據庫,這樣日誌信息就會被寫入到mysql.slow.log表中。同時也支持兩種日誌存儲方式,配置的時候以逗號隔開即可,如:log_output='FILE,TABLE'。

日誌記錄到系統的專用日誌表中,要比記錄到文件耗費更多的系統資源。因此對於需要啓用慢查詢日誌,又需要能夠獲得更高的系統性能,那麼建議優先記錄到文件

6.3.2 log-queries-not-using-indexes

該系統變量指定未使用索引的查詢也被記錄到慢查詢日誌中(可選項)。如果調優的話,建議開啓這個選項。另外,開啓了這個參數,其實使用full index scan的SQL也會被記錄到慢查詢日誌。

6.4  mysqldumpslow工具

在生產環境中,如果要手工分析日誌,查找、分析SQL,顯然是個體力活。MySQL提供了日誌分析工具mysqldumpslow。

 

七、存儲過程

7.1  概念

MySQL 5.0 版本開始支持存儲過程。存儲過程(Stored Procedure)是一種在數據庫中存儲複雜程序,以便外部程序調用的一種數據庫對象。存儲過程是爲了完成特定功能的SQL語句集,經編譯創建並保存在數據庫中,用戶可通過指定存儲過程的名字並給定參數(需要時)來調用執行。存儲過程思想上很簡單,就是數據庫 SQL 語言層面的代碼封裝與重用。

爲什麼要用存儲過程?

①將重複性很高的一些操作,封裝到一個存儲過程中,簡化了對這些SQL的調用

②批量處理:SQL+循環,減少流量

③統一接口,確保數據的安全

相對於oracle數據庫來說,MySQL的存儲過程相對功能較弱,使用較少。

優點

  • 存儲過程可封裝,並隱藏複雜的商業邏輯。
  • 存儲過程可以回傳值,並可以接受參數。
  • 存儲過程無法使用 SELECT 指令來運行,因爲它是子程序,與查看錶,數據表或用戶定義函數不同。
  • 存儲過程可以用在數據檢驗,強制實行商業邏輯等。

缺點

  • 存儲過程,往往定製化於特定的數據庫上,因爲支持的編程語言不同。當切換到其他廠商的數據庫系統時,需要重寫原有的存儲過程。
  • 存儲過程的性能調校與撰寫,受限於各種數據庫系統。

7.2 創建一個簡單的存儲過程

1、創建存儲過程的簡單語法

create procedure 名稱()
begin
.........
end

2、創建一個簡單的存儲過程

create procedure testa()
begin
    select * from users;
    select * from orders;
end;

 3、調用存儲過程

call testa();   

    

八、binlog

8.1 什麼是binlog?

binlog記錄了所有的DDL和DML語句(除了數據查詢語句select),以事件形式記錄,還包含語句所執行的消耗的時間,MySQL的二進制日誌binlog可以說是MySQL最重要的日誌,MySQL的二進制日誌是事務安全型的。

DDL(Data Definition Language 數據定義語言):主要的命令有create、alter、drop等,ddl主要是用在定義或改變表(table)的結構,數據類型,表之間的連接和約束等初始工作上,他們大多在建表時候使用。

DML(Data Manipulation Language 數據操縱語言):主要命令是slect,update,insert,delete,就像它的名字一樣,這4條命令是用來對數據庫裏的數據進行操作的語言。

binlog日誌包括兩類文件:

  1. 二進制日誌索引文件(文件名後綴爲.index)用於記錄所有的二進制文件。
  2. 二進制日誌文件(文件名後綴爲.00000*)記錄數據庫所有的DDL和DML(除了數據查詢語句select)語句事件。

8.2 binlog使用場景

  1. mysql主從複製:mysql replication在master端開啓binlog,master把它的二進制日誌傳遞給slaves來達到master-slave數據一致的目的。
  2. 數據恢復:通過mysqlbinlog工具來恢復數據。所謂恢復,就是讓mysql將保存在binlog日誌中指定段落區間的sql語句逐個重新執行一次而已。

8.3 binlog的開啓

在my.inf主配置文件中直接添加三行:

# 打開binlog日誌
log_bin=ON
# binlog日誌的基本文件名,後面會追加標識來表示每一個文件
log_bin_basename=/var/lib/mysql/mysql-bin
# binlog文件的索引文件,這個文件管理了所有的binlog文件的目錄
log_bin_index=/var/lib/mysql/mysql-bin.index
# 隨機指定一個不能和其他集羣中機器重名的字符串,如果只有一臺機器,那就可以隨便指定了
server-id=123454

有了上述的配置之後,我們就可以重新啓動我們的mysql了,重啓命令:

service mysqld restart

啓動成功之後,我們可以登陸查看我們的配置是否起作用:

show variables like '%log_bin%'

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章