MySql 基礎到進階優化
-
讀寫鎖
讀鎖是共享的,或者說是互不阻塞,多個客戶端在同一時刻可以讀取同一個資源,寫鎖是排他的一個寫鎖會阻塞其他的寫鎖和讀鎖.
-
表鎖
表鎖是mysql中最基本的鎖策略,並且是開銷最小的策略.
-
行級鎖
行級鎖可以支持最大程度的併發處理,同時也帶來了最大的鎖開銷.
-
事務
ACID 原子性,一致性,隔離性,持久性
- 原子性 - 一個事物必須被視爲一個不可分割的最小工作單元,整個事務中所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事物來說,不可能只執行其中的以部分操作,這就是事物的原子性.
- 一致性 - 數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態.
- 隔離性 - 通常來說,一個事物所做的修改在最終提交以前,對其他事物是不可見的.
- 持久性 - 一旦事物提交,所做的修改就會永久保存到數據庫中.
-
隔離級別
- read uncommitted (未提交讀) 在read uncommitted級別,事物中的修改,即使沒有提交,對其他事物也是可見的.事物可以讀取未提交的數據,也被稱爲髒讀(Dirty Read).
- read committed (提交讀) 大多數數據庫系統默認的隔離級別都是read committed(但是mysql不是).read committed 滿足隔離性的簡單定義: 一個事物開始時,只能看見已經提交的事物所做的修改.一個事物從開始直到提交之前,所做的任何修改對其他事物都是不可見的,這個級別的也叫做不可重複讀(nonrepeatable),因爲執行兩次同樣的查詢,可能獲得的結果不一樣
-
repeatable read(重讀)
- 它解決了髒讀問題.該級別保證了同一個事物中多次讀取同樣記錄的結果是一致的.它是mysql默認的隔離級別.但是這個還是無法解幻讀(phantom read)的問題.所謂幻讀,就是指當某個事物在讀取某個範圍內的記錄時,另外一個事物又在該範圍內插入了新的記錄,當之前的事物在此讀取該範圍的記錄時,會產生幻行.
-
serializable
- serializable 是最高級別的隔離級別.它通過強制事物串行執行,避免了前面說的幻讀.簡單來說,serializable會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用的問題.
-死鎖
死鎖是指兩個或者多個事物在統一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致的惡性循環的現象.當多個事物視圖以不同的順序鎖定資源時,就可能會產生死鎖的情況.
mysql 中的事物
mysql中提供了兩種事務型的存儲引擎: InnoDB 和 NDB Cluster
-
自動提交(autocommit)
mysql默認採用自動提交模式.也就是說,如果不是顯式地開始一個事物,則每個查詢都被當作一個事物執行提交操作.在當前連接中,可以通過設置autocommit變量來啓用或者禁用自動化個提交模式
選擇優化的數據類型
-
更小的更好
一般情況下,應該儘量使用可以正確存儲數據的最小類型
-
簡單就好
簡單的數據類型付出的代價更低
- 儘量避免NULL
NULL的列使索引,索引統計和值比較都更爲複雜
MySql爲了兼容性支持很多別名,例如integer,bool,numeric.其實它們都是基本類型
整數類型
有tinyint,smallint,mediumint,int,bigint.
MySql可以爲整數類型指定寬度,但是它不會限制合法範圍,對於存儲和計算來說,INT(1)和INT(20)是相同的
實數類型
實數是帶有小數部分的數字
例如在財務存儲中,數據量較大時,可以考慮使用BIGINT代替DECIMAL,將需要存儲的貨幣單位根據小數的位數乘以相應的倍數即可.假設存儲財務數據要精確到萬分之一,則可以把所有的金額乘以一百萬,然後將結果存儲在BIGINT裏,同時避免了浮點存儲計算不精準和DECIMAL精確計算但是代價高的問題
字符串類型
VARCHAR類型用於存儲可變長字符串
CHAR類型是定長的
BLOB 和 TEXT 類型
兩者都是爲存儲很大的數據而設計的字符串類型數據,分別採用二進制和字符方式存儲
最好的解決方案是儘量避免使用,如果實在無法避免,有一個技巧實在所有用到BLOB字段的地方都使用SUBSTRING(colum,length)將列值轉換爲字符串(在 ORDER BY 子句中也適用)
日期和時間類型
Mysql能存儲的最小時間粒度爲秒(MariaDB支持微秒級別的時間類型),MySql也同樣可以使用微秒來進行臨時運算.
-
DATETIME
這個類型能保存的最大範圍值,從1001年到9999年,精度爲秒,它把日期和時間封裝格式爲YYYYMMDDHHMMSS
的整數中,與時區無關,使用8個字節存儲空間 -
TIMESTAMP
它保存了從1970年1月1日午夜以來的秒數.只使用了4個字節
如果想使用更小級別的時間值,可以使用BIGINT來存儲時間戳
UUID
如果存儲UUID贏移除-
符號,用UNHEX()
轉換UUID爲16字節的數字,並且存儲在BINARY(16)
的列中.檢索時可以通過HEX()
函數來格式化爲十六進制格式
特殊類型數據
例如IPv4地址,可以使用無符號整數存儲,MySql提供INET_ATON()
和INET_NTOA()
函數來轉換
MyS schema設計中的陷阱
-
太多的列
-
太多的關聯
MySql限制了每個關聯操作最多只能有61張表,單個查詢最好在12個表以內
- 避免用 NULL
可以使用0,某個字符串,或者空字符串作爲代替
- 更快的讀,更慢的寫
爲了提升讀查詢的速度,經常會使用一些額外索引,增加冗餘列,甚至是創建緩存表和彙總表,這些方法會增加寫查詢的負擔,也需要額外的維護任務,但在設計高性能數據庫時,都是常見技巧
快速創建MyISAM索引
爲了高效載入數據到MyISAM表中,常用技巧是先禁用索引,載入數據,然後重新啓用索引
ALTER TABLE test.load_data DISABLE KEYS;
-- load data
ALTER TABLE test.load_data ENABLE KEYS;
該技巧對唯一索引無效
創建高性能索引
MySql只能高效實用索引的最左前綴列
索引類型
B-Tree索引
B-Tree 意味着通常所用的值都是按順序存儲的,並且每一個葉子頁到根的距離相同,B-Tree索引能夠加快訪問數據的速度,因爲存儲引擎不再需要進行全表掃描來獲取需要的數據,取而代之的是從索引的根節點開始搜索.
B-Tree對索引列使順序組織存儲的,所有很適合查找範圍數據
B-Tree索引適用於全鍵值,鍵值範圍或者鍵前綴查找
- 如果不是按照索引最左開始查找.則無法使用索引
- 不能跳過索引中的列
- 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查找
全值匹配指的是和索引中的所有的列進行匹配
哈希索引
哈希索引基於哈希表實現,只有精確匹配索引所有列的查詢纔能有效.
對於每一行數據,存儲引擎都會對所有的索引計算一個哈希碼,哈希碼是一個較小的值,並且不同鍵值的行計算出來的哈希碼也不一樣.哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針
在MySql中,只有Memory引擎顯式支持哈希索引,這也是Memory引擎表的默認索引類型
- 哈希索引質保函哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行.
- 哈希索引數據並不是按照索引值順序存儲的,所以無法用於排序
- 哈希索引也不支持部分索引列匹配查找,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的
- 哈希索引只支持等值比較查詢,包括
=
IN()
,也不支持任何範圍查詢 例如WHERE price > 100
- 訪問哈希索引的數據非常快,除非有很多哈希衝突(不同的索引列值卻有相同的哈希值)
- 如果哈希衝突很多的話,一些索引維護操作的代價也會很高
空間數據索引(R-Tree)
MyISAM 表支持空間索引,可以用作地理數據存儲.和B-Tree索引不同,這類索引無須前綴查詢.空間索引會從所有維度來索引數據.查詢時,可以有效地使用任意維度來組合查詢.
全文索引
全文索引是一種特殊類型的索引,它查找的是文本中的關鍵字,而不是直接比較索引中的值.全文搜索和其他幾類索引的匹配方式完全不一樣.
查詢性能優化
查詢性能低下最基本的原因是訪問的數據太多.某些查詢可能不可避免地需要篩選大量數據.大部分性能低下的查詢都可以通過減少訪問的數據量的方式進行優化.對於低效的查詢,我們可以通過兩個步驟來分析:
- 確認應用程序是否檢索大量超過需要的數據
- 確認MySQL服務器層是否在分析大量超過需要的數據行
有些查詢會請求超過實際需要的數據,然後這些多餘的數據會被應用程序丟棄.這會給MySql服務器帶來額外的負擔,並增加網絡開銷.
- 避免查詢不必要的記錄
- 多表關聯時不要返回不必要的字段
- 不要總是取出全部列
- 避免重複查詢
- 分解關聯查詢
MySql的查詢狀態
-
Sleep 線程正在等待客戶發送新的請求
-
Query 線程正在執行查詢或者正在將結果發送給客戶端
-
Locked 在MySql服務器層,該線程正在等待表鎖
-
Analyzing and statistics 線程正在收集存儲引擎統計信息,並且生成查詢的執行計劃
-
Copying to tmp table [on disk] 線程正在執行查詢,並且將結果集都複製到一個臨時表彙總,如果帶有
on disk
標記,那表示MySql正在講一個內存臨時表放到磁盤上 -
Sorting result 線程正在對結果集進行排序
-
Sending Data 線程可能在多個狀態之間傳送數據,或者正在生成結果集,或者在向客戶端發送數據
查詢優化器
-
重新定義關聯表的順序
-
將外連接轉化成內連接
並不是所有的OUTER JOIN語句都必須以外連接的方式執行,例如:WHERE條件,庫表結構都可能會讓外連接等價於一個內連接 -
使用等價變換規則
MySql可以使用一些等價變換來簡化並規範表達式.它可以合併和減少一些比較,還可以移除一些恆成立和一些恆不成立的判斷.例如,(5=5 AND a>5)將被改寫而成a>5
- 優化 COUNT(),MIN()和MAX()
索引和列是否爲空通常可以幫助MySql優化這類表達式.例如,要找某一列的最小值,只需要查詢對應B-Tree索引最左端的記錄.類似的,沒有任何WHERE條件的COUNT(*) 查詢通常也可以使用存儲引擎提供的一些優化
- 預估並且轉化爲常數表達式
當MySql檢測到一個表達式可以轉化爲常數的時候,就會一直把該表達式作爲常數進行優化處理
關聯子查詢
一般建議使用左外連接(LEFT OUTER JOIN)來代替子查詢
UNION的限制
有時,MySql無法將限制條件從外層下推到內層,這使得原本能夠限制部分返回結果的條件無法應用到內層查詢優化上.
如果希望UNION的各個子句能夠根據LIMIT只取部分結果集,或者希望能夠先排好序再合併結果集的話,就需要在UNION的各個子句中分別使用這些子句.
優化COUNT()查詢
- 簡單優化
select count(*) from test where id > 5
我們可以這樣優化
select (select count(*) from test) - count(*) from test where id <=5
這其中的子查詢會被當做一個常數來處理
優化LIMIT分頁
優化此類分頁查詢的一個最簡單的辦法就是儘可能地使用索引覆蓋掃描.
select id from test order by name limit 50,5;
可以有以下幾種方式
select id from test where position between 50 and 54 order by position;
select * from test where id < 999 order by id desc limit 20;