MySQL重要知識點(總結)

最近一段時間都學習mysql,將重要的知識點總結如下:

一、字段、表、索引設計規範

1、字段設計規範
① 字段類型優先選擇符合存儲需要的最小類型

字段類型優先級:整型>date;time >enum>char;varchar>blob

原因:整型,time運算快,節省內存;enum列內部是用整型存儲的,char,varchar要考慮字符集的轉換和排序的校對集,速度慢;blob無法使用臨時表。

② 夠用就行(如smallint,varchar(N))

原因:大的字段浪費內存,影響速度,如varchar(10),varchar(300),雖然存儲的內容一樣,但是,在表聯查時,varchar(300)要花更多內存

③ 儘量避免使用允許爲null()

原因:null不利於索引,要用特殊的字節標註,在磁盤上佔的空間其實更大
例子:建兩個相同字段的表,一個允許爲null,一個不允許。可以發現爲null的索引更大些。

null  測試 .PNG

④ 避免使用ENUM類型

修改ENUM值需要使用ALTER語句
ENUM類型的ORDER BY操作效率低,需要額外操作
禁止使用數值作爲ENUM的枚舉值

⑤ 使用TIMESTAMP(4個字節)或DATETIME類型(8個字節)存儲時間

TIMESTAMP 存儲的時間範圍 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07
TIMESTAMP 佔用4字節和INT相同,但比INT可讀性高
超出TIMESTAMP取值範圍的使用DATETIME類型存儲

⑥ 金額類數據使用decimal

Decimal類型爲精準浮點數,在計算時不會丟失精度
佔用空間由定義的寬度決定,每4個字節可以存儲9位數字,並且小數點要佔用一個字節
可用於存儲比bigint更大的整型數據

2、表設計規範
① 定長與變長分離

核心且常用字段,宜建成定長,放在一張表.
而varchar, text,blob,這種變長字段,適合單放一張表, 用主鍵與核心表關聯起來.

② 常用字段和不常用字段要分離.

需要結合網站具體的業務來分析,分析字段的查詢場景,查詢頻度低的字段,單拆出來.

3、索引設計規範
① 限制每張表中索引的數量,建議單張表中的索引不超過5個

索引並不是越多越好!索引可以提高效率同樣可以降低效率
因爲mysql優化器在選擇如何優化查詢時,會根據統一信息,對每一個可以用到的索引來進行評估,以生成出一個最好的執行計劃,如果同時有很多個索引都可以用於查詢,就會增加mysql優化器生成執行計劃的時間,同樣會降低查詢性能

② 禁止給表的每一列都建立單獨索引

5.6版本之前,一個sql只能使用到一個表中的一個索引,5.6以後,雖然有了合併索引的優化方式,但是還是遠遠沒有使用一個聯合索引的查詢方式好

③禁止給表的每一列都建立單獨索引

5.6版本之前,一個sql只能使用到一個表中的一個索引,5.6以後,雖然有了合併索引的優化方式,但是還是遠遠沒有使用一個聯合索引的查詢方式好

④每個Innodb表必須有一個主鍵

Innodb是一種索引組織表:數據的存儲的邏輯順序和索引的順序是相同的
每個表都可以有多個索引,但是表的存儲順序只能有一種Innodb是按照主鍵索引的順序來組織表的,不要使用更新頻繁的列作爲主鍵
不要使用UUID,MD5,HASH,字符串列作爲主鍵(無法保證數據的順序增長),主鍵建議使用自增ID值

二、事務

事務保證一組原子性的操作,要麼全部成功,要麼全部失敗。一旦失敗,回滾之前的所有操作。MySql採用自動提交,如果不是顯式的開啓一個事務,則每個查詢都作爲一個事務。

1、事務的四大特性
①原子性

事務的原子性,要麼全部完成,要麼完全不起作用

②隔離性

併發訪問數據庫時,一個用戶的事務不被其他事務所幹擾,數據庫中各個事務相互獨立

③持久性

一個事務被提交後,改變是持久的

④一致性

執行事務前後,數據保持一致,多個事務對同一個數據庫讀取的結果是相同的

2、併發事務帶來哪些問題
  • ######髒讀(Dirty read):

當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。

  • ######丟失修改(Lost to modify)

指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那麼在第一個事務中修改了這個數據後,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱爲丟失修改。

  • ######不可重複讀(Unrepeatableread)

指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。

  • 幻讀(Phantom read)

幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。

不可重複度和幻讀區別:不可重複讀的重點是修改,幻讀的重點在於新增或者刪除

3、事務的隔離級別有哪些
  • READ-UNCOMMITTED(讀取未提交)

最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀。

  • READ-COMMITTED(讀取已提交)

允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生。

  • REPEATABLE-READ(可重複讀)

對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。

  • SERIALIZABLE(可串行化)

最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。

隔離級別
MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看
當前數據庫的隔離級別

這裏需要注意的是:與 SQL 標準不同的地方在於InnoDB 存儲引擎在 REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-Key Lock 鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server)是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL標準的SERIALIZABLE(可串行化)隔離級別。

因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):,但是你要知道的是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。

InnoDB 存儲引擎在 分佈式事務 的情況下一般會用到SERIALIZABLE(可串行化)隔離級別。

三、鎖

數據庫通過鎖機制來解決併發場景-共享鎖(讀鎖)和排他鎖(寫鎖)。讀鎖是不阻塞的,多個客戶端可以在同一時刻讀取同一個資源。寫鎖是排他的,並且會阻塞其他的讀鎖和寫鎖。簡單提下樂觀鎖和悲觀鎖。

  • 樂觀鎖:通常用於數據競爭不激烈的場景,多讀少寫,通過版本號和時間戳實現。
  • 悲觀鎖:通常用於數據競爭激烈的場景,每次操作都會鎖定數據。

要鎖定數據需要一定的鎖策略來配合。

  • 表鎖:鎖定整張表,開銷最小,但是會加劇鎖競爭。
  • 行鎖:鎖定行級別,開銷最大,但是可以最大程度的支持併發。

但是MySql的存儲引擎的真實實現不是簡單的行級鎖,一般都是實現了多版本併發控制(MVCC)。MVCC是行級鎖的變種,多數情況下避免了加鎖操作,開銷更低。MVCC是通過保存數據的某個時間點快照實現的。

四、存儲引擎

1、查看MySQL當前默認的存儲引擎

我們也可以通過下面的命令查看默認的存儲引擎。

mysql> show variables like '%storage_engine%';

查看錶的存儲引擎

通過下面的命令查看錶的存儲引擎

show table status like "table_name" ;
2、 MyISAM和InnoDB區別

MyISAM是MySQL的默認數據庫引擎(5.5版之前)。雖然性能極佳,而且提供了大量的特性,包括全文索引、壓縮、空間函數等,但MyISAM不支持事務和行級鎖,而且最大的缺陷就是崩潰後無法安全恢復。不過,5.5版本之後,MySQL引入了InnoDB(事務性數據庫引擎),MySQL 5.5版本後默認的存儲引擎爲InnoDB。

大多數時候我們使用的都是 InnoDB 存儲引擎,但是在某些情況下使用 MyISAM 也是合適的比如讀密集的情況下。(如果你不介意 MyISAM 崩潰回覆問題的話)。

① 功能差異
② 存儲差異
③ 選擇差異

這塊詳細介紹可參考:我的另一篇《優化流程圖》

五、大表優化當MySQL

單表記錄數過大時,數據庫的CRUD性能會明顯下降,一些常見的優化措施如下

1. 限定數據的範圍

務必禁止不帶任何限制數據範圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的範圍內;

2. 讀/寫分離

數據量增多,單機的數據庫不足以支撐業務,需要用到數據庫集羣。而讀寫分離,就是將數據庫的讀和寫分離,對應到數據庫一般就是主從數據庫,一主一從或者一主多從;業務服務器把數據寫到主數據庫中,讀操作都去從庫讀;主庫會同步數據到從庫,保證數據的一致性。

主從集羣
  這種集羣方式,就是將訪問的壓力從主庫轉移到從庫,單機的數據庫不能支撐併發讀寫的時候,而且讀的請求很多的情況下就適合數據庫集羣。如果寫的操作很多的話,那就不適合這種集羣方式,因爲寫的時候是寫入主庫,主庫的壓力還是沒有變化,同時同步到從庫也需要消耗資源。

單機的時候,一般數據庫優化就是加索引,但是加了索引對查詢有優化,但是寫入的時候會有影響,因爲寫入的數據庫不會更新索引。所以在主從數據庫中,可以單獨的對讀庫(從數據庫)做索引,而寫庫(主數據庫)可以減少縮影而提高寫的效率。

但是主從數據庫中需要注意:主從同步延遲、分配機制的考慮
①主從同步延遲
  • 二次讀取

一般操作是,在從庫讀取數據時,沒有讀到數據,就去主庫進行數據讀取。但是這種操作還是將讀的壓力返還給主庫,如果有惡意的攻擊,主庫就爆了。
一般情況下,通過對數據庫訪問的API進行封裝就能實現這個功能,業務之間沒有耦合。

  • 寫了之後的馬上讀操作操作主庫

在寫了數據後,立馬讀操作就去訪問主庫,之後的讀操作訪問從庫,這種業務上會有高度耦合。

  • 根據業務,將重要的業務數據的讀寫都放在主庫,其他的業務進行讀寫分離

類似付錢的這種業務,讀寫都到主庫,避免延遲的問題,但是例如改個頭像啊,個人簽名這種比較不重要的就讀寫分離,查詢都去從庫查。

② 分配機制的考慮

分配機制的考慮,就是怎麼制定去操作去主庫寫,去從庫讀。
*數據庫中間件

有專門的獨立系統來實現讀寫分離和數據庫連接管理,業務服務器和數據庫中間件通過SQL協議交流,在業務服務器看起來,數據庫中間件就是一個數據庫。

因爲是通過sql協議的所以可以兼容不同的語言不需要單獨寫一套,並且有中間件來實現主從切換,業務服務器不需要關心這點。但是多了一個服務,就會有其他的消耗,難於管理,而且中間件性能要求高,實現複雜,難度高。

開源的中間件有Mysql Proxy,Atlas。

數據庫中間件

>讀寫分離理解和實現相對簡單,但是隻能減少訪問的壓力,不能分擔存儲的壓力,當數據增多是,查詢的速度還是會很慢。這時候就需要用到分庫分表了。

正常情況下,單機不能支撐業務時,纔會用數據庫集羣,軟件設計越簡單的設計越好。

一般數據庫的優化是,先優化一些查詢操作,然後優化業務的邏輯,或者加入緩存,最後不行再用集羣,最後再分庫分表。

3. 垂直分區

根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。

簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 如下圖所示,這樣來說大家應該就更容易理解了。
垂直拆分

  • 垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易於維護。
  • 垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;
4、水平拆分

保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。

水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數據量過大對性能造成影響。
水平拆分
水平拆分能夠 支持非常大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨節點Join性能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對數據進行分片,因爲拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中間件的網絡I/O。

六、索引優化

1、常見索引列建議
① 出現在SELECT、UPDATE、DELETE語句的WHERE從句中的列,包含在ORDER BY、GROUP BY、DISTINCT中的字段,多表join的關聯列。並不要將符合1和2中的字段的列都建立一個索引, 通常將1、2中的字段建立聯合索引效果更好。
  • 舉個栗子:
    在where條件常用的列上都加上索引
    例: where cat_id=3 and price>100 ; //查詢第3個欄目,100元以上的商品
    誤: cat_id上,和, price上都加上索引.
    錯: 只能用上cat_id或Price索引,因爲是獨立的索引,同時只能用上1個.
② 避免建立冗餘索引、重複索引
③ 索引的列如果是表達式的一部分或者是函數的參數,則失效
④ 針對特別長的字符串,可以使用前綴索引,根據索引的選擇性選擇合適的前綴長度(詳細參考:如何選擇合適的前綴長度)
⑤ 使用多列索引的時候,可以通過 AND 和 OR 語法連接
⑥ 將範圍查詢放在條件查詢的最後,防止範圍查詢導致的右邊索引失效的問題
⑦ 索引最好不要選擇過長的字符串,而且索引列也不宜爲null
⑧ 對於頻繁的查詢優先考慮覆蓋索引
2、選擇索引列順序

*區分度最高的放在聯合索引的最左側(區分度=列中不同值的數量/列的總行數)
*儘量把字段長度小的列放在聯合索引的最左側(因爲字段長度越小,一頁能存儲的數據量越大,IO性能也就越好)
*使用最頻繁的列放到聯合索引的左側(這樣可以比較少的建立一些索引)
*舉個栗子
在多列上建立索引後,查詢哪個列,索引都將發揮作用
誤: 多列索引上,索引發揮作用,需要滿足左前綴要求.
以 index(a,b,c) 爲例

索引使用情況

3、分析explain執行計劃
EXPLAIN SELECT settleId FROM Settle WHERE settleId = "3679"

查看explain計劃

  • select_type,有幾種值:simple(表示簡單的select,沒有union和子查詢),primary(有子查詢,最外面的select查詢就是primary),union(union中的第二個或隨後的select查詢,不依賴外部查詢結果),dependent union(union中的第二個或隨後的select查詢,依賴外部查詢結果)

  • type,有幾種值:system(表僅有一行(=系統表),這是const連接類型的一個特例),const(常量查詢), ref(非唯一索引訪問,只有普通索引),eq_ref(使用唯一索引或組件查詢),all(全表查詢),index(根據索引查詢全表),range(範圍查詢)

  • possible_keys: 表中可能幫助查詢的索引

  • key,選擇使用的索引

  • key_len,使用的索引長度

  • rows,掃描的行數,越大越不好

  • extra,有幾種值:Only index(信息從索引中檢索出,比掃描錶快),where used(使用where限制),Using filesort (可能在內存或磁盤排序),Using temporary(對查詢結果排序時使用臨時表)

七、語句優化建議

1. 建議使用預編譯語句進行數據庫操作

預編譯語句可以重複使用這些計劃,減少SQL編譯所需要的時間,還可以解決動態SQL所帶來的SQL注入的問題。
只傳參數,比傳遞SQL語句更高效。
相同語句可以一次解析,多次使用,提高處理效率。

2. 避免數據類型的隱式轉換

隱式轉換會導致索引失效如:

select name,phone from customer where id = '111';
3. 充分利用表上已經存在的索引

避免使用雙%號的查詢條件。如:a like ‘%123%’,(如果無前置%,只有後置%,是可以用到列上的索引的)
一個SQL只能利用到複合索引中的一列進行範圍查詢。如:有 a,b,c列的聯合索引,在查詢條件中有a列的範圍查詢,則在b,c列上的索引將不會被用到。
在定義聯合索引時,如果a列要用到範圍查找的話,就要把a列放到聯合索引的右側,使用left join 或 not exists 來優化not in 操作,因爲not in 也通常會使用索引失效。

4. 禁止使用SELECT * 必須使用SELECT <字段列表> 查詢

原因:消耗更多的CPU和IO以網絡帶寬資源
無法使用覆蓋索引
可減少表結構變更帶來的影響

5. 避免使用不含字段列表的INSERT語句
如:
insert into values ('a','b','c');
應使用:
insert into t(c1,c2,c3) values ('a','b','c');
6. 避免使用子查詢,可以把子查詢優化爲join操作

通常子查詢在in子句中,且子查詢中爲簡單SQL(不包含union、group by、order by、limit從句)時,纔可以把子查詢轉化爲關聯查詢進行優化。

子查詢性能差的原因:子查詢的結果集無法使用索引,通常子查詢的結果集會被存儲到臨時表中,不論是內存臨時表還是磁盤臨時表都不會存在索引,所以查詢性能會受到一定的影響。特別是對於返回結果集比較大的子查詢,其對查詢性能的影響也就越大。
由於子查詢會產生大量的臨時表也沒有索引,所以會消耗過多的CPU和IO資源,產生大量的慢查詢。

7. 避免使用JOIN關聯太多的表

對於Mysql來說,是存在關聯緩存的,緩存的大小可以由join_buffer_size參數進行設置。
在Mysql中,對於同一個SQL多關聯(join)一個表,就會多分配一個關聯緩存,如果在一個SQL中關聯的表越多,所佔用的內存也就越大。
如果程序中大量的使用了多表關聯的操作,同時join_buffer_size設置的也不合理的情況下,就容易造成服務器內存溢出的情況,就會影響到服務器數據庫性能的穩定性。
同時對於關聯操作來說,會產生臨時表操作,影響查詢效率,Mysql最多允許關聯61個表,建議不超過5個。

8. 減少同數據庫的交互次數

數據庫更適合處理批量操作,合併多個相同的操作到一起,可以提高處理效率。

9. 對應同一列進行or判斷時,使用in代替or

in 的值不要超過500個,in 操作可以更有效的利用索引,or大多數情況下很少能利用到索引。

10. 禁止使用order by rand() 進行隨機排序

order by rand()會把表中所有符合條件的數據裝載到內存中,然後在內存中對所有數據根據隨機生成的值進行排序,並且可能會對每一行都生成一個隨機值,如果滿足條件的數據集非常大,就會消耗大量的CPU和IO及內存資源。
推薦在程序中獲取一個隨機值,然後從數據庫中獲取數據的方式。

11. WHERE從句中禁止對列進行函數轉換和計算

對列進行函數轉換或計算時會導致無法使用索引

  • 不推薦:
where date(create_time)='20190101'
  • 推薦:
where create_time >= '20190101' and create_time < '20190102'
12. 在明顯不會有重複值時使用UNION ALL 而不是UNION

UNION 會把兩個結果集的所有數據放到臨時表中後再進行去重操作
UNION ALL 不會再對結果集進行去重操作

13. 拆分複雜的大SQL爲多個小SQL

大SQL邏輯上比較複雜,需要佔用大量CPU進行計算的SQL
MySQL中,一個SQL只能使用一個CPU進行計算
SQL拆分後可以通過並行執行來提高處理效率

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