MYSQL開發規範html
日期mysql |
版本算法 |
說明sql |
修訂數據庫 |
|
|
|
|
2016.03.20數組 |
1.0緩存 |
mysql開發規範v1.0安全 |
錘子服務器 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
修訂歷史記錄網絡
目錄
MYSQL開發規範.................................................... 1
1. 引言.......................................................... 3
1.1 背景及目的................................................... 3
1.2 適用範圍..................................................... 3
2. 數據庫對象命名規範............................................ 3
2.1 原則......................................................... 3
2.2 命名規範..................................................... 3
3. 數據庫對象設計規範............................................ 6
3.1 存儲引擎的選擇............................................... 6
3.2 字符集的選擇................................................. 6
3.3 數據庫設計規範............................................... 7
3.4 表設計規範................................................... 7
3.5 字段設計規範................................................. 8
3.6 索引設計規範................................................. 9
3.7 約束設計規範................................................ 10
4. SQL編寫規範.................................................. 10
4.1 數據類型轉換規範............................................ 10
4.2 SELECT * 使用規範........................................... 10
4.3 字段上使用函數使用規範...................................... 11
4.4 錶鏈接規範.................................................. 12
4.5 分頁查詢規範................................................ 12
4.6 從庫多SQL線程複製規範...................................... 13
4.7 隨機取數規範................................................ 14
4.8 其餘規範.................................................... 14
5. 高效的設計模型............................................... 15
5.1 設計原則說明................................................ 15
5.2正則化與非正則化的選擇....................................... 18
5.3表容量設計和數據切分......................................... 18
5.4索引設計..................................................... 21
6.SQL優化指導................................................... 33
6.1select子句優化方法........................................... 33
6.2join聯接的優化方法........................................... 41
6.3數據更新語句優化............................................. 43
6.4子查詢優化................................................... 44
6.5優化器相關explain以及經常使用hint介紹.......................... 48
7.經常使用函數...................................................... 54
7.1字符串函數................................................... 54
7.2日期函數..................................................... 54
7.3類型轉換函數................................................. 55
隨着合商雲購電子商務有限公司業務的發展,使用MySQL數據庫的系統和應用數量不斷擴大,爲了提升數據庫效率,實現標準化開發及便於數據庫的統一管理,制定本規範。
本規範適用於合商雲購電子商務有限公司全部與MySQL相關的開發人員、數據庫管理員與運營人員。
命名規範是指數據庫對象如數據庫(SCHEMA)、表(TABLE)、索引(INDEX)、約束(CONSTRAINTS)等的命名約定。
1
n 命名使用具備意義的英文詞彙,詞彙中間如下劃線分隔。
n 命名只能使用英文字母、數字、下劃線。
n 避免用MySQL的保留字如:call、group等。
n 全部數據庫對象使用小寫字母。
1
2
2.1
2.2
數據庫命名規則以下:
項目簡稱+1位數據庫類型代碼+識別代碼+序號
數據庫類型代碼:
1) T:業務型數據庫
2) A:分析型數據庫
3) H:歷史數據庫
識別代碼:
1) DEV:開發數據庫
2) TEST:測試數據庫
若是一種類型的數據庫一個數據庫,則不加序號,不然末尾增長序號。
若是是生產庫則不加識別代碼,不然須要增長愛識別代碼DEV或TEST
若是隻做歷史庫,部分生產、開發或者測試,則只須要項目簡稱+H+序號
舉例:
出入系統業務生產庫:AOCT、AOCT一、AOCT2
出入系統業務開發庫:AOCTDEV、AOCTDEV一、AOCTDEV2
出入系統業務測試庫:AOCTTEST、AOCTTEST一、AOCTTEST2
n 數據庫名不能超過30個字符。
n 數據庫命名必須爲項目英文名稱或有意義的簡寫。
n 數據庫建立時必須添加默認字符集和校對規則子句。默認字符集爲UTF8。示例見設計規範。
n 命名應使用小寫。
數據庫對象的命名應該以最少的字母達到最容易理解的意義。若是沒有特殊規定,數據庫對象及其屬性的命名應知足以下條件:
1) 命名不推薦使用保留字;
2) 數據庫實體統一採用英文命名;
3) 對象命名長度最好不要超過30個字符,縮寫要易於理解,符合通用的習慣,例如部門編碼縮寫:dept_code,組織機構編碼縮寫:org_code。
4) 前導字符爲A至Z
5) 非前導字符能夠爲:
l A至Z
l 0至9
l _(下劃線字符)
附: MySQL中Unicode字符集列表:
字符集名稱 |
字節佔用 |
字符集兼容性 |
Unicode字符支持 |
UCS2 |
每字符2字節 |
|
全部Unicode 3.0字符 |
UTF16 |
每字符2字節,或4字節。 |
與UCS2兼容 |
全部Unicode 5.0和Unicode 6.0字符,包括擴展字符。 |
UTF16LE |
與UTF16相同,只是字節順序相反。 |
|
全部Unicode 5.0和Unicode 6.0字符,包括擴展字符。 |
UTF8 |
每字符1到3字節。 |
|
全部Unicode 3.0字符 |
UTF8MB4 |
每字符1到4字節。 |
與UTF8兼容 |
全部Unicode 5.0和Unicode 6.0字符,包括擴展字符。 |
UTF32 |
每字符4字節。 |
|
全部Unicode 5.0和Unicode 6.0字符,包括擴展字符。 |
n 同一個模塊的表儘量使用相同的前綴,表名稱儘量表達含義。
n 多個單詞如下劃線(_)分隔。
n 表名不能超過30個字符。
n 普通表名以t_開頭,表示爲table,命名規則爲t_模塊名(或有意義的簡寫)_+table_name。
n 臨時表(運營、開發或數據庫人員臨時用做臨時進行數據採集用的中間表)命名規則:加上tmp前綴和8位時間後綴(tmp_test_user_20130501)。
n 備份表(運營、開發或數據庫人員備份用做保存歷史數據的中間表)命名規則:加上bak前綴和8位時間後綴(bak_test_user_20130501)。
n 命名應使用小寫。
n 字段命名須要表示其實際含義的英文單詞或簡寫,單詞之間用下劃線(_)進行鏈接。
n 各表之間相贊成義的字段必須同名。
n 字段名不能超過30個字符。
n 經常使用約定:
序號列字段:以Id後綴,如:UserId表示用戶編號。
編碼字段:以Code後綴,如:CustCode表示客戶編碼。
時間字段:
1)精確到日的字段,以Date做爲後綴。如:OpenDate表示開戶日期。
2)精確到秒或毫秒的,以T_time做爲後綴。如:RregisterTime表示註冊時間。
布爾值字段:命名以「Is」前綴+字段描述。如Member表上表示爲Enabled的會員的列命名爲IsEnabled。
n 命名應使用帕斯卡(pascal)命名法。
n 視圖名以v_模塊名開頭,表示view。
n 視圖名不能超過30個字符。如超過30個字符則取簡寫。
n 命名應使用小寫。
n 存儲過程名以proc_開頭,表示procedure。以後多個單詞如下劃線(_)進行鏈接。存儲過程命名中應體現其功能。存儲過程名不能超過30個字符。
n 存儲過程當中的輸入參數以i開頭,輸出參數以o開頭。
n 命名應使用小寫。
n 函數名以func_開頭,表示function。以後多個單詞如下劃線(_)進行鏈接,函數命名中應體現其功能。函數名不能超過30個字符。
n 函數中輸入參數以i開頭,輸出參數以o開頭。
n 命名應使用小寫。
n 觸發器以tri_開頭,表示trigger。
n 基本部分,描述觸發器所加的表,觸發器名不能超過30個字符。
n 後綴(_i,_u,_d),表示觸發條件的觸發方式(insert,update或delete)。
n 如無特殊須要,嚴禁開發人員使用觸發器。
n 命名應使用小寫。
n 二級(輔助)索引以idx_開頭,惟一索引以uidx_開頭。後面緊跟索引所在的字段名。如要在id列上添加二級索引,則應爲idx_id。
n 多單詞組成的列名,取儘量表明意義的縮寫,如test_contact表member_id和friend_id上的組合索引:idx_mid_fid。
n 組合索引命名應注意字段順序。如在字段member和字段userid上建立組合索引,則能夠命名爲idx_userid_member(‘userid’,‘member’)
n 命名應使用小寫。
n 惟一約束: uidx_表名稱_字段名。
n 外鍵約束:fk_表名,後面緊跟該外鍵所在的表名和對應的主表名(不含t_).子表名和父表名用下劃線(_)分隔。
n 非空約束:如無特殊須要,建議全部字段默認非空,不一樣數據類型必須給出默認值。
n 出於性能考慮,如無特殊須要,建議不使用外鍵。參照完整性由代碼控制。
n 命名應使用小寫。
說明:
執行用戶:表示腳本在哪一個用戶下運行。例如腳本是在deployop用戶下執行,則執行用戶爲deployop。
對象類型:表示是對數據庫的什麼對象類型(例如table)做的操做。不一樣對象類型的操做必須放在不一樣的文件中。
操做類型:包括DDL、DML。不一樣操做類型的sql腳本不能放在同一個文件中。
n DDL文件的命名:這次需求序列號(系統工單號即SR號)+執行順序號+腳本執行用戶+對象類型縮寫+客戶化的信息+mysql .sql。
例如:id5771_01_deployop_table_xiechuanjiang419_mysql.sql
n DML文件的命名:這次需求序列號(系統工單號即SR號)+執行順序號+腳本執行用戶+dml+客戶化的信息+mysql.sql。
例如:id5771_02_deployop_dml_xiechuanjiang419_mysql.sql
n 不一樣數據庫的DB腳本要分開編寫。
n DML語句必須顯示加commit語句。
因MYSQL是輕量級數據庫,各(開發、測試、生產)環境的數據庫名可能會不一樣,因此腳本文件中操做(建立、刪除、訪問)表時不能加數據庫名。
移交的腳本中不能經過use database指定數據庫名。
在實際執行腳本前,必須使用use database指定到當前實例對應的數據庫上。
2
MySQL支持數個存儲引擎做爲對不一樣表的類型的處理器,MySQL中的插件式存儲引擎架構是很是有特點的亮點。如無特殊要求,默認使用innodb存儲引擎,該引擎爲5.6版本中的默認存儲引擎。
MySQL引擎 |
說明 |
InnoDB |
索引和數據均可以緩存到內存中; 支持事務; 支持行級鎖,可實現更高的併發度; 支持故障恢復; 支持外鍵約束; 支持4種不一樣的事務隔離級別; |
n 自開發系統的數據庫Utf8做爲字符集的唯一選擇。
n 外購系統的字符集按照開發同事要求選擇,需申請例外。
n 在開發環境中編寫的建庫建表腳本及使用工具導出的數據腳本文件自己,必須在導出工具中,顯式選擇utf8做爲導出格式。
n 在開發環境中編寫的建庫建表腳本及使用工具導出的數據腳本文件,如在進庫前須要編輯,必須使用純文本方式打開,編輯和保存,防止隱含控制字符(如^M)添加進腳本。在Linux環境,能夠經過 「cat -A腳本文件名」方式確認和檢查是否攜帶了隱含控制字符。
n 控制單庫表個數,建議單庫不超過2048個表。
n 建立數據庫的語句必須包含字符集子句和校對規則子句。如:
create database [if not exists]
default character set UTF8MB4 default collate utf8mb4_bin;
n 不一樣組件間所對應的數據庫表之間的關聯應儘量減小,若是不一樣組件間的表須要外鍵關聯也儘可能不要建立外鍵關聯,而只是記錄關聯表的一個主鍵,確保組件對應的表之間的獨立性,爲系統或表結構的重構提供可能性。
n 表設計的角度不該該針對整個系統進行數據庫設計,而應該根據系統架構中組件劃分,針對每一個組件所處理的業務進行組件單元的數據庫設計。
n 表必需要有PK。
n 一個字段只表示一個含義。
n 表不該該有重複列,好比,年月日用不一樣的字段設計是不容許的。
n 老是包含兩個字段:created_date(建立日期),updated_date (修改日期),且這兩個字段不該該包含有額外的業務邏輯,在建立或修改記錄的時候,必須建立或修改這兩個字段
示例:
created_time Datetime not null comment '建立日期' ,
updated_time Datetime not null comment '修改日期' ,
n 禁止使用複雜數據類型(數組,自定義等)。
n 須要join的字段(鏈接鍵),數據類型必須保持絕對一致,避免隱式轉換。
n 設計應至少知足第三範式,儘可能減小數據冗餘。一些特殊場景容許反範式化設計,但在項目評審時須要對冗餘字段的設計給出解釋。
n TEXT字段必須放在獨立的表中,用PK與主表關聯。如無特殊須要,禁止使用TEXT、BLOB字段。
n 須要按期刪除(或者轉移)過時數據的表,經過分表解決。
n 單表字段數不要太多,建議最多不要大於50個。
n MySQL在處理大表時,性能就開始明顯下降,因此建議單表物理大小限制在16GB,表中數據控制在2000W內。
n 若是數據量或數據增加在前期規劃時就較大,那麼在設計評審時就應加入分表策略。
n 無特殊需求,嚴禁使用分區表。
n INT:如無特殊須要,存放整型數字使用UNSIGNED INT型。整型字段後的數字表明顯示長度。整型類型以下表:
數據類型 |
最大存儲長度(有符號) |
最大存儲長度(無符號) |
tinyint(m) |
1個字節 範圍(-128~127) |
1個字節 範圍(0~256) |
smallint(m) |
2個字節 範圍(-32768~32767) |
2個字節 範圍(0~65535) |
mediumint(m) |
3個字節 範圍(-8388608~8388607) |
3個字節 範圍(0~16777215) |
int(m) |
4個字節 範圍(-2147483648~2147483647) |
4個字節 範圍(0~4294967294) |
bigint(m) |
8個字節 範圍(+-9.22*10的18次方) |
8個字節 範圍(0~1.84*10的20次方) |
n DECIMAL(M,D):定點小數使用此DECIMAL類型,且明確標識出爲無符號型(UNSIGNED),除非確實會出現負數。
n DATE:全部只須要精確到天的字段所有使用DATE類型,而不該該使用TIMESTAMP或者DATETIME類型。
n DATETIME:全部須要精確到時間(時分秒)的字段均使用DATETIME,不要使用TIMESTAMP類型。
n VARCHAR:全部動態長度字符串 所有使用VARCHAR類型,相似於狀態等有限類別的字段,也使用能夠比較明顯表示出實際意義的字符串,而不該該使用INT之類的數字來代替;VARCHAR(N),N表示的是字符數而不是字節數。好比VARCHAR(255),能夠最大可存儲255個字符(字符包括英文字母,漢字,特殊字符等)。但N應儘量小,由於MySQL一個表中全部的VARCHAR字段最大長度是65535個字節,且存儲字符個數由所選字符集決定。如UTF8存儲一個字符最大要3個字節,那麼varchar在存放佔用3個字節長度的字符時不該超過21845個字符。同時,在進行排序和建立臨時表一類的內存操做時,會使用N的長度申請內存。
如無特殊須要,原則上單個varchar型字段不容許超過255個字符。
n CHAR:僅僅只有單個字符的字段使用CHAR(1)類型,例如性別字段。如無特殊須要,建議INNODB引擎不使用CHAR型 。
n TEXT:僅僅當字符數量可能超過20000個的時候,才能夠使用TEXT類型來存放字符類數據,由於全部MySQL數據庫都會使用UTF8字符集。全部使用TEXT類型的字段必須和原表進行分拆,與原表主鍵單獨組成另一個表進行存放。如無特殊須要,嚴禁開發人員使用MEDIUMTEXT、TEXT、LONGTEXT類型。
n 使用INT UNSIGNED型存儲IPV4。PHP程序推薦使用long型存儲IPV4(非強制)。
n 對於精確浮點型數據存儲,須要使用DECIMAL,嚴禁使用FLOAT和DOUBLE。
n 如無特殊須要,嚴禁開發人員使用BLOB類型。
n 如無特殊須要,字段必須使用NOT NULL屬性,可用默認值代替NULL。MySQL NULL類型和Oracle的NULL有差別,會進入索引中。此外,NULL在索引中的處理也是特殊的,也會佔用額外的存放空間。
n 不建議使用ENUM、SET類型,使用TINYINT來代替。
n 每一個列定義的時候必須加上comments。
n 自增字段類型必須是整型且必須爲UNSIGNED,推薦類型爲INT或BIGINT,而且自增字段必須是主鍵或者主鍵的一部分。
n 日期類型的字段不能使用VARCHAR或者CHAR類型,只能使用DATE、DATETIME字段類型存放。
n 索引必須建立在索引選擇性選擇性較高的列上。
索引選擇性:索引列中不一樣值的數目與表中記錄數的比值。如SEX字段共100條記錄,只存放男、女兩個值,則在SEX列上建立的索引idx_sex的索引選擇性爲2/100=0.02
MySQL沒有位圖索引。
n 組合索引的首字段,必須在where條件中。
n 對於肯定須要組成組合索引的多個字段,建議將選擇性高的字段靠前放。
n 禁止使用外鍵,容易產生死鎖,應由程序保證參照完成性。
n Text類型字段必須使用前綴索引。
n 單張表的索引數量理論上應控制在5個之內。常常有大批量插入、更新操做表,應儘可能少建索引。
n 組合索引中的字段數建議不超過5個。
n ORDER BY,GROUP BY,DISTINCT的字段須要添加在索引的後面,造成覆蓋索引。
n 禁止對過長(MySQL的varchar索引只支持不超過768個字節,UTF8是三字節,即:768/3=256,因此字段最長爲255)的VARCHAR類型字段創建索引,除前綴索引外超過32字節的varchar列加索引須要DBA評估。若是須要對超過32字節的varchar列整列進行徹底匹配,須要新增一個字段,該字段是varchar列的md5值,使用md5來進行徹底匹配;這種狀況下,就沒法進行範圍查詢。
n PK應該是有序而且無心義的,儘可能由開發人員自定義,且儘量短,使用自增序列。
n 表中除PK之外,還存在惟一性約束的,能夠在數據庫中建立以「uidx_」做爲前綴的惟一約束索引。
n PK字段不容許更新。
n 禁止建立外鍵約束,外鍵約束應有應用程序控制。
n 如無特殊須要,全部字段必須添加非空約束。
n 如無特殊須要,全部字段必須有默認值。
3
4.1.
在全部Query的Where條件中必須使用和過濾字段徹底一致的數據類型,杜絕任何隱式類型轉換,避免形成由於數據類型不匹配而致使Query執行計劃的出錯,形成性能問題。
全部Where條件的字段上不容許使用函數作類型轉換,若有須要轉換類型,只能轉換數值或變量代入值,而不是轉換字段。
最爲常見的隱式類型轉換常見於時間類型與字符串類型之間,建議全部時間類型字段均以時間類型傳入,或者以字符串傳入而後經過字符串轉換時間函數進行轉換,以下:
select * from member
where member_create =str_to_date('20150701 01:02:03','%Y%m%d %H:%i:%s');
在錶鏈接Query中,若是鏈接條件兩端的數據類型不一致,必須保證將驅動表的鏈接條件數據類型轉換爲與被驅動表一致的數據類型。
select * from test_info a,test_customer b
where a.order_id =cast(b.order_id as unsigned int) and a.pay_type= 'cash ';
4.2.
嚴禁使用select *進行查詢。
n MySQL數據庫進行Order By操做有兩種算法:
² 第一種是直接取出全部須要返回字段(select後面的字段),存入內存中,而後排序(僅有須要排序的字段須要參與)。
² 第二種是先取出須要排序的字段,而後排序,再回表中取出其餘的字段,就至關於全部數據都有兩次磁盤IO。表如今MYSQL執行計劃中爲FILESORT。
若是select後面的字段長度總和超過1024字節(即參數max_length_for_sort_data的默認值)或者字段中包括BLOB、TEXT字段,都將會使用第二種算法。因此order by的查詢不容許使用select *。
n join語句使用select *可能致使只須要訪問索引便可完成的查詢須要回表取數,因此禁止使用。
mysql> explain SELECTt1.a,t2.aFROM t1 LEFT JOIN t2 ON t1.a =t2.a;
+----+-------------+-------+-------+---------------+-----------+---------+--------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-----------+---------+--------------+------+-------------+
| 1 | SIMPLE | t1 | index | NUL | idx_t1_ac | 66 | NUL | 5 |Using index|
| 1 | SIMPLE | t2 | ref | idx_t2_a | idx_t2_a | 33 | test1.t1.a | 1 |Using index|
+----+-------------+-------+-------+---------------+-----------+---------+--------------+------+-------------+
mysql> explain SELECT*FROM t1 LEFT JOIN t2 ON t1.a =t2.a;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------+
| 1 | SIMPLE | t1 | ALL | NUL | NULL | NUL | NULL | 5 |NUL |
| 1 | SIMPLE | t2 | ALL | idx_t2_a | NULL | NUL | NULL | 5 |Using where; Using join buffer (Block Nested Loop)|
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------+
n MySQL中的text類型字段和Oracle的clob同樣,存儲的時候不是和由其餘普通字段類型的字段組成的記錄存放在一塊兒,並且讀取效率自己也不如普通字段塊。若是不須要取回text字段,又使用了select * ,會讓完成相同功能的sql所消耗的io量大不少,並且增長部分的io效率也更低下。
4.3.
在取出字段上能夠使用相關函數(儘量避免出現now(),rand(),sysdate(),current_user()等不肯定結果的函數),可是在Where條件中的過濾條件字段上嚴禁使用任何函數,包括數據類型轉換函數。
n 語句級(STATEMENT)複製場景下,引發主從數據不一致;不肯定值的函數,產生的SQL語句沒法利用QUERY CACHE。
n 錯誤的寫法:
Select member_create
from .. .
Where date_format(member_create, '%Y%m%d %H:%i:%s')= '20150701 00:00:0'
n 正確的寫法:
Select date_format(member_create, '%Y%m%d %H:%i:%s')
from .. .
Where member_create =str_to_date('20150701 00:00:00', '%Y%m%d %H:%i:s');
4.4.
全部鏈接的SQL必須使用Join ... On ...方式進行鏈接,而不容許直接經過普通的Where條件關聯方式。外鏈接的SQL語句,能夠使用Left Join ... On的Join方式,且全部外鏈接一概寫成Left Join,而不要使用Right Join。
如無特殊須要,嚴禁開發人員使用STRAIGHT_JOIN。
n 錯誤的寫法:
select a.id,b.id from a,b where a.id = b.a_id and ...
n 正確的寫法:
select a.id,b.id from a inner join b on a.id = b.a_id where ...
4.5.
分頁查詢語句所有都須要帶有排序條件,除非業務方明確要求不要使用任何排序來隨機展現數據。
常規分頁語句寫法(start:起始記錄數,page_offset:每頁記錄數,步長):
select *
from table_a t
order by test_modified desc limit start, page_offset
多表Join的分頁語句,若是過濾條件在單個表上,內查詢語句必須走覆蓋索引,先分頁,再Join:
n 錯誤的寫法:
select a.column_a, a.column_b .. . b.column_a, b.column_b .. .
from table_t a, table_b b
where a.xxx.. .
and a.column_c = b.column_d
order bya.yyy limit start, page_offset
n 正確的寫法:
select a.column_a, a.column_b .. . b.column_a, b.column_b .. .
from (select t.column_a, t.column_b .. .
from table_t t
where t.xxx.. .
order by t.yyy limit
start, page_offerset) a,
table_b b
where a.column_c = b.column_d;
4.6.
使用從庫多SQL線程複製時必須保證一個事務只能包括單個數據庫的相關對象。
n 錯誤的寫法:
start transaction;
Update customer.customer_info set banlance= banlance-100 where customer_id=123;
Insert apply.apply_info values(apply_id,customer_id,apply_date,amount)
values(9900,123, str_to_date('20130801 00:00:00', '%Y%m%d %H:%i:s'),100) ;
commit;
n 正確的寫法:
應該將customer_info表和apply_info表放在一個數據庫裏面,若是涉及到分庫分表,也是按照customer_id將各個表的數據拆分到不一樣的數據庫裏面。
4.7.
嚴禁在MYSQL數據庫中使用RAND()函數生成隨機數,嚴禁開發人員直接使用ORDER BY RAND()取隨機數,而應在應用中使用其餘方法替換。ORDER BY rand()會將數據從磁盤中讀取,進行排序,會消耗大量的IO和CPU。
下面給出的是非必要寫法。嚴禁直接在MYSQL中使用RAND()函數。
n 錯誤的寫法:
Select id,name,age,sex
from member
order by rand() limit 10;
n 正確的寫法:
SELECT id,name,age,sex
FROM `member`
WHERE id >= (SELECT floor(RAND() * (SELECT MAX(id) FROM `member`)))
ORDER BY id LIMIT 10;
n 進行大批量操做時必須分批提交,每次數據量操做不能超過10W條。
n last_insert_id()函數只能返回當前session最近一次insert操做以後所使用到的auto_increment類型字段的值,且使用"select last_insert_id()",不要再跟一個"from table_name"。
n WHERE條件中嚴禁在索引列上進行數學運算或函數運算。
n 用in() /union替換or,並注意in的個數小於300。
n 嚴禁使用%前綴進行模糊前綴查詢,能夠使用%模糊後綴查詢。如:select id,val from table where val like ‘name%’。
n 使用prepared statement,能夠提升性能而且避免SQL注入。
n 嚴禁開發人員使用LOCK TABLE語句人爲加鎖;僅容許使用SELECT * .. FOR UPDATE語句。
n Where條件儘量避免非等值條件,in,between,<,<=,>,>=會致使後面的條件使用不了索引。
n 使用union all 代替union。排序操做應當在union all前的子查詢中執行。union all不須要對結果集再進行排序。
n Update,delete語句不要使用limit,不然可能會致使主從架構不一致,同時錯誤信息會記錄到錯誤日誌,佔用大量空間。
n Insert 語句必須指明字段名稱,避免後期由於字段擴展,影響原有應用。
n Insert語句使用bulk提交,values的個數不該該過多。bulk提交能夠提升寫效率,但個數過多,數據恢復須要時間過長。
n 拆分複雜的SQL爲多個小SQL,避免大事務。簡單的SQL容易使用到MYSQL的QUERY CACHE;減小鎖表時間特別是MYISAM;能夠使用多核CPU。
n 儘可能採用批量SQL語句。
a)INSERT ... ON DUPLICATE KEY UPDATE
b)REPLACE INTO
c)INSERT IGNORE
d)INSERT INTO VALUES()
n 對同一表的屢次alter操做必須合併爲一次操做
嚴禁開發人員使用alter語法。
MYSQL對錶的修改絕大部分操做都須要鎖表並重建表,而鎖表則會對線上業務形成影響。爲減小這種影響,必須把對錶的屢次alter操做合併爲一次操做。例如,要給表t增長一個字段b,同時給已有的字段aa創建索引, 一般的作法分爲兩步:
alter table t add column b varchar(10);
而後增長索引:
alter table t add index idx_aa(aa);
正確的作法是:
alter table t add column b varchar(10),add index idx_aa(aa);
數據庫設計是指對於一個給定的應用環境,構造合理的數據庫模式,創建數據庫及其應用系統,有效存儲數據,知足用戶信息要求和處理要求。
數據庫設計在開發過程當中處於一個很是重要的地位。一個高效的數據庫模型是很是重要和必要的。
4
5.1.
數據庫完整性是指數據庫中數據的正確性和相容性。數據庫完整性是由完整性約束來保證的。數據庫完整性約束經過DBMS或者應用程序來實現的。
數據庫完整性對於數據庫應用系統很是關鍵,其做用主要體如今如下幾個方面:
n 數據庫完整性約束可以防止合法用戶使用數據庫時向數據庫中添加不合語義的數據。
n 利用基於DBMS的完整性控制機制來實現業務規則,易於定義,容易理解,並且能夠下降應用程序的複雜性,提升應用程序的運行效率。同時,基於DBMS的完整性控制機制是集中管理的,所以比應用程序更容易實現數據庫的完整性。
n 合理的數據庫完整性設計,可以同時兼顧數據庫的完整性和系統的效能。好比裝載大量數據時,只要在裝載以前臨時使基於DBMS的數據庫完整性約束失效,此後再使其生效,就能保證既不影響數據裝載的效率又能保證數據庫的完整性。 如:在導入大量數據時,能夠在建立表時不添加主鍵外的其餘索引,能夠獲得明顯的寫入性能提高。
n 在應用軟件的功能測試中,完善的數據庫完整性有助於儘早發現應用軟件的錯誤。
完整性主要包括實體完整性,參照完整性以及用戶定義完整性。它們的實現機制以下:
n 實體完整性:主鍵
n 參照完整性:
² 父表中刪除數據:級聯刪除;受限刪除;置空值
² 表中插入數據:受限插入;遞歸插入
² 父表中更新數據:級聯更新;受限更新;置空值
² DBMS對參照完整性能夠有兩種方法實現:外鍵實現機制(約束規則)和觸發器實現機制
n 用戶定義完整性:指針對某一具體關係數據庫的約束條件,它反映某一具體應用所涉及的數據必須知足的語義要求,好比表中利用check關鍵字定義age的取值範圍。
瞭解如上的基本知識外,在設計數據庫完整性時,有一些原則須要注意:
n 根據數據庫完整性約束的類型肯定其實現的系統層次和方式,並提早考慮對系統性能的影響。通常狀況下,靜態約束應儘可能包含在數據庫模式中,而動態約束由應用程序實現。其中靜態約束主要是指數據庫肯定狀態時的數據對象所應知足的約束條件,它是反映數據庫狀態合理性的約束,這是最重要的一類完整性約束,好比列取值,列類型,空值等的約束。而動態約束主要是指數據庫從一種狀態轉變爲另外一種狀態時,新、舊值之間所應知足的約束條件,它是反映數據庫狀態變遷的約束,好比一些用戶自定義類的完整性約束。
n 實體完整性約束、參照完整性約束是關係數據庫最重要的完整性約束,在不影響系統關鍵性能的前提下可考慮使用。
n 要慎用目前主流DBMS都支持的觸發器功能,一方面因爲觸發器的性能開銷較大,另外一方面,觸發器的多級觸發很差控制,容易發生錯誤。
n 要根據業務規則對數據庫完整性進行細緻的測試,以儘早排除隱含的完整性約束間的衝突和對性能的影響。
n 爲了在數據庫和應用程序代碼之間提供另外一層抽象,能夠爲應用程序創建專門的視圖而沒必要非要應用程序直接訪問數據表。這樣作還等於在處理數據庫變動時給你提供了更多的自由。
性能是衡量一個系統的關鍵因素,在設計階段就在性能方面就應該多關注,儘可能減小後期的煩惱。
在數據庫設計階段,性能上的考慮時須要注意:不能以範式做爲惟一標準或者指導,在設計過程當中,須要從實際需求出發,以性能提高爲根本目標來展開設計工做,一些時候爲了提高性能,甚至會作反範式設計。
另外還有一些設計上的方法和技巧:
n 設置合理的字段類型和長度。字段類型在知足需求後應儘可能短,好比,能用int就儘可能不要用bigint。另外不一樣數據庫在varchar和text類型在長度和性能上也是不一樣的,選擇時要謹慎。
n 選擇高效的主鍵和索引。因爲對錶記錄的讀取都是直接或者間接地經過主鍵或索引來獲取,所以應該該根據具體應用特性來設計合理的主鍵或索引。同時索引長度的也應該關注,儘可能減小索引長度。
n 適度冗餘。適度的冗餘能夠避免關聯查詢,減小join查詢。
n 精簡表結構。表結構若是太過複雜,會引發業務上處理複雜,同時也可能會引發併發問題。若是根據業務特性拆分紅多個表,能夠避免高併發下的鎖表現象。
在大規模系統中,除了性能,可擴展性也是設計的關鍵點,而數據庫表擴展性主要包含表邏輯結構、功能字段的增長、分表等。在擴展性上要把握的原則以下:
n 一表一實體。若是不一樣實體之間有關聯時,可增長一個單獨的表,不會影響之前的功能。
n 擴展字段。在表數據較小時增長一個字段能夠很快完成,可是在表很大時,增長字段會比較困難。所以在設計時可考慮選擇預留擴展字段。
n 分表設計。也就是水平切分。在設計階段應該考慮數據的增加狀況,並根據數據特性以及數據之間關係選擇合適的切分策略。有關分表的更詳細介紹具體可詳見章節3.1.5的介紹
5.2.
數據庫正則化是指消除冗餘、有效組織數據、減小在數據操做期間潛在的不規則和提升數據一致性。在正則化數據庫中,每一個元素只會被存儲一次,而在非正則化數據庫中,信息是重複的或者保存在多個地方的。
好比在設計消費者與帳號信息表時,若是消費者的信息好比姓名、聯繫方式等都存儲在這張消費者與帳號信息表中時,當「張三」有多個帳號時,「張三」的基本信息就被屢次存儲,這樣設計是不符合正則化的。
正則化包括根據設計規則建立表並在這些表間創建關係;經過取消冗餘度與不一致相關性,該設計規則能夠同時保護數據並提升數據的靈活性。
通常狀況下正則化有以下優勢:
n 正則化更新一般比非正則化更新快。
n 當數據被很好正則化後,數據量不多,重複數據小,更新量也會變少。
n 正則化表一般較小,更容易裝入內存性能所以也會更好。
固然正則化也是存在一些缺點:在正則化結構上的非通常性查詢,通常都須要至少一個聯接。
非正則化因爲數據都在一個表中,避免了聯接,同時也能運行更有效率的索引策略,所以性能不錯。
固然非正則化在數據冗餘,數據獨立性,相關性以及提升數據一致性方面稍差。
正則化和非正則化各有利弊,在具體應用中一般是結合兩種方案。
5.3.
隨着數據庫表中數據日積月累愈來愈多,數據庫會愈來愈大,表記錄數也會達到千萬甚至億級別,數據庫表的訪問效率降低明顯,致使外層應用的訪問效率很是差,訪問時間急劇上升,用戶體驗降低。此時就必須使用數據切分來解決這個瓶頸了。
數據切分就是指經過某種特定的條件,將存放在同一個數據庫中的數據分散存放到多個數據庫上面,以達到分散單臺設備負載的效果。數據切分根據切分規則的類型,能夠分爲兩種模式。一種是按照不一樣的表切分到不一樣的數據庫上或不一樣的表上,這種切分稱爲垂直切分;另一種是根據表中數據的邏輯關係,將同一個表的數據按照某種規則切分到多臺數據庫上或不一樣的表上,這種稱爲水平切分。
垂直切分就是要把表按模塊劃分到不一樣數據庫或不一樣表中。這種切分在大型網站的演變過程當中是很常見的。當一個網站還在很小的時候,只有少許的人來開發和維護,各模塊和表都在一塊兒,當網站不斷豐富和壯大的時候,也會變成多個子系統來支撐,這時就有按模塊和功能把表劃分出來的需求。
一個架構設計較好的應用系統,其整體功能確定是由多個功能模塊所組成的,而每個功能模塊所須要的數據對應數據庫中的就是一張表或者多個表。在架構設計中,各個功能模塊互相之間的交互點越統1、越少,系統的耦合度就越低,系統各個模塊的維護性及擴展性也就越好。這樣的系統,實現數據的垂直切分也就越容易。好比公司的軒轅系統中,直銷模塊和工做流模塊數據庫本來在一塊兒的,但隨着推廣工做進行,致使該數據庫的壓力較大,所以對直銷模塊和工做流模塊進行了拆分工做。因爲前期設計時這兩個模塊之間的耦合度較低,在拆分過程當中也很順利。
功能模塊越清晰,耦合度越低,數據垂直切分的規則定義也就越容易。徹底能夠根據功能模塊來進行數據切分,不一樣功能模塊的數據存放在不一樣的數據庫或不一樣表上,能夠很容易就避免跨庫的join存在,同時系統架構也是很是清晰的。
固然,很難有系統可以作到全部功能模塊使用的表徹底獨立,根本不須要訪問對方的表,或者不須要將兩個模塊的表進行join操做。這種狀況下,就必須根據實際的應用場景來進行評估權衡。遷就應用程序就須要將待join的表的相關模塊存放在同一個數據庫表中,或者讓應用程序作更多的事情——徹底經過模塊接口取得不一樣數據庫表中的數據,而後在程序中完成join操做。
若是讓多個模塊集中共用數據源,實際上也是間接默認了各模塊架構耦合度增大的發展,可能會惡化之後的架構。
因此,在數據庫進行垂直切分的時候,如何切分、切分到什麼樣的程度,是一個比較考驗人的難題,這隻能在實際應用場景中經過平衡各方面的成本和收益,才能分析出一個真正合適本身的切分方案。
垂直切分的優缺點:
n 優勢:
² 數據庫表的切分簡單明瞭,切分規則明確;
² 應用程序模塊清晰明確,整合容易;
² 數據維護方便易行,容易定位;
n 缺點:
² 部分表關聯沒法在數據庫級別完成,須要在程序中進行;
² 對於訪問極其頻繁且數據量超大的表仍然存在性能瓶頸,不必定能知足要求;
² 事務處理相對複雜;
² 切分達到必定程度以後,擴展性會受到限制;
² 過分切分可能會致使系統過於複雜而難以維護;
水平切分能夠簡單理解爲按照數據行的切分,就是將表中的某些行切分到一個數據庫或表,而另外的某些行又能夠切分到其餘的數據庫或表中。爲了可以比較容易斷定各行數據被切分到哪一個庫或表中,切分須要按照某種特定的規則進行。
水平切分的切分標準能夠按照數據範圍分,好比1-100萬一個表,100萬-200萬又是一個表;也能夠按照時間順序來切分,好比一年的數據歸到一張表中等;也能夠按照地域範圍來分,好比按照地市來分,每一個或多個地市一個庫等;也能夠按照某種計算公式來切分,比較簡單的好比取模的方式,如根據用戶id進行水平切分,可經過對ID被2取模,而後分別存放在不一樣的表中,這樣關聯時也很是方便。公司著名的鳳巢拆庫就是採用取模方式進行的拆分。
水平切分的優缺點:
n 優勢:
² 表關聯基本可以在數據庫端所有完成;
² 不會存在某些超大型數據量和高負載的表遇到瓶頸的問題;
² 應用程序端總體架構改動相對較少;
² 事務處理相對簡單;
² 只要切分規則可以定義好,擴展性通常不會受到限制;
n 缺點:
² 切分規則相對複雜,很難抽象出一個能知足整個數據庫的切分規則;
² 後期的維護難度有所增長,人爲手工定位數據較困難。
² 應用系統各模塊耦合度很是高,可能會對後面數據的遷移切分形成必定的困難。
² 若切分不合理,會形成數據表的冷熱不均現象。
由上面可知垂直切分能更清晰化模塊劃分,區分治理,水平切分能解決大數據量性能瓶頸問題。本節將結合垂直切分和水平切分的優缺點,進一步完善總體架構,並提升系統的擴展性。
在實際的應用場景中,除了那些負載並非太大、業務邏輯相對簡單的系統能夠經過上面兩種切分方法之一來解決擴展性問題,可是大部分的業務邏輯複雜、系統負載大的系統,都沒法經過上面任何一種數據的切分方法來實現較好的擴展性。這就須要將上述兩種方法結合使用,不一樣的場景使用不一樣的切分方法。
每個應用系統的負載都是一步一步增加起來的,在開始遇到性能瓶頸時,大多架構師和DBA都會選擇數據的垂直切分。然而隨着業務的不斷擴張,系統負載的持續增加,在系統穩定一段時間以後,通過垂直切分的數據庫集羣可能再次不堪重負,遇到性能瓶頸。
這時再進一步細分模塊?隨着模塊的不斷細化,應用系統的架構會愈來愈複雜,整個系統極可能會出現失控的局面。這時就必須利用數據水平切分的優點來解決問題。在垂直切分的基礎上利用水平切分來避開垂直切分的弊端,解決系統不斷擴大的問題。而水平切分的弊端也已經被以前的垂直切分解決了。
在大型的應用系統上,垂直和水平切分基本上是並存的,並且是不斷的交替進行的,以增長系統的擴展能力。咱們在應對不一樣的應用場景時就要充分考慮這兩種切分方法的侷限及優點,在不一樣的時期使用不一樣的方法。
聯合切分的優缺點:
n 優勢
² 能夠充分利用垂直和水平切分各自的優點而避免各自的缺陷;
² 讓系統擴展性獲得最大化提高。
n 缺點
² 數據庫系統架構會比較複雜,維護難度更大;
² 應用程序架構也更復雜。
5.4.
存儲引擎使用了不一樣的方式把B-Tree索引保存到磁盤上,它們會表現出不一樣的性能。例如MyISAM使用前綴壓縮的方式以減少索引;而InnoDB不會壓縮索引。同時MyISAM的B-Tree索引按照行存儲的物理位置來引用被索引的行,可是InnoDB按照主鍵值引用行。這些不一樣有各自的優勢和缺點。
1
2
3
4
5
5.1
5.2
5.3
5.4
5.4.1
聚簇索引不是一種單獨的索引類型,而是一種存儲數據的方式。當表有聚簇索引的時候,它的數據行實際保存在索引的葉子頁。聚簇是指實際的數據行和相關的鍵值都保存在一塊兒。每一個表只能有一個聚簇索引。
因爲是存儲引擎負責實現索引,並非全部的存儲引擎都支持聚簇索引。當前只有SolidDB和InnoDB是惟一支持聚簇索引的存儲引擎。
數據與索引在同一個B-Tree上,通常數據的存儲順序與索引的順序一致。InnoDB cluster index每一個葉子節點包含primary key和行數據,非葉子節點只包括被索引列的索引信息。
聚簇索引的優缺點:
n 優勢:
² 相關的數據保存在一塊兒,利於磁盤存取;
² 數據訪問快,由於聚簇索引把索引和數據一塊兒存放;
² 覆蓋索引能夠使用葉子節點的primary key的值使查詢更快;
n 缺點:
² 若是訪問模式與存儲順序無關,則聚簇索引沒有太大的用處;
² 按主鍵順序插入和讀取最快,可是若是按主鍵隨機插入(特別是字符串)則讀寫效率下降;
² 更新聚簇索引的代價較大,由於它強制InnoDB把每一個更新的行移到新的位置;
² 創建在聚簇索引上的表在插入新行,或者在行的主鍵被更新,該行必須被移動的時候會進行分頁。分頁發生在行的鍵值要求行必須被放到一個已經放滿了數據的頁的時候,此時存儲引擎必須分頁才能容納該行,分頁會致使表佔用更多的磁盤空間。
² 聚簇表可能會比全表掃描慢,尤爲在表存儲的比較稀疏或由於分頁而沒有順序存儲的時候。
² 非聚簇索引可能會比預想的大,由於它們的葉子節點包含了被引用行的主鍵列。
² 非聚簇索引訪問須要兩次索引查找,而不是一次。
其它須要說明點:
InnoDB的primary key爲cluster index,除此以外,不能經過其餘方式指定cluster index,若是InnoDB不指定primary key,InnoDB會找一個unique not null的field作cluster index,若是尚未這樣的字段,則InnoDB會建一個非可見的系統默認的主鍵---row_id(6個字節長)做爲cluster_index。
建議使用數字型auto_increment的字段做爲cluster index。不推薦用字符串字段作cluster index (primary key) ,由於字符串每每都較長, 會致使secondary index過大(secondary index的葉子節點存儲了primary key的值),並且字符串每每是亂序。cluster index亂序插入容易形成插入和查詢的效率低下。
InnoDB中非cluster index的全部索引都是secondary index。
secondary index的查詢代價變大,須要兩次B-Tree查詢,一次secondary index,一次cluster index。因此在創建cluster index和secondary index的時候須要考慮到這點。
當secondary index知足covering index(參見3.5.1.4章節介紹)時,只須要一次B-Tree查詢而且直接在secondary index即可獲取所需數據,不須要再進行數據讀取,提升了效率。咱們在設計索引和寫SQL語句的時候就能夠考慮利用到covering index的優點。
建議儘可能減小對primary key的更新,由於secondary index葉子節點包含primary key的value (這樣避免當row被移動或page split時更新secondary index), primary key的變化會致使全部secondary index的更新。
動態哈希索引是InnoDB爲了加速B-Tree上的節點查找而保存的hash表 。B-Tree上常常被訪問的節點將會被放在動態哈希索引中。
注意點:
MySQL重啓後的速度確定會比重啓前慢,由於InnoDB的innodb_buff_pool和adaptive hash index都是內存型的,重啓後消失,須要預熱(訪問一段時間)後性能才能慢慢上來。
索引一般是用於找到行的,但也能夠用於找到某個字段的值而不須要讀取整個行,由於索引中存儲了被索引字段的值,只讀索引不讀數據,這種狀況下的索引就叫作覆蓋索引。
覆蓋索引是頗有力的工具,能夠極大地提升性能。它主要的優點以下:
n 索引記錄一般遠小於全行大小,所以只讀索引,MySQL就能極大的減小數據訪問量。這對緩存的負載是很是重要的,它大部分的響應時間都花在拷貝數據上。對於I/O密集型的負載也有幫助。由於索引比數據小不少,能很好的裝入內存。
n 索引是按照索引值來進行排序的,所以I/O密集型範圍訪問將會比隨機地從磁盤提取每行數據要快的多。
n 覆蓋索引對於InnoDB來講很是有用,由於InnoDB的彙集緩存。InnoDB的輔助索引在葉子節點保存了主鍵值,所以,覆蓋了查詢的第二索引在主鍵上避免了另一次索引查找。
5.4.2
在MySQL中,索引只能從字段內容的最左端開始建, 查詢的時候也只能從索引的最左端開始查, 對字段內容只建從左開始的部分字節的索引,而非所有作索引的這種index就叫作前綴索引(prefix index)。
前綴索引的優缺點:
n 優勢
在索引知足必定的區分度的狀況下,索引變得更小,更有利於放入或將更多的索引放入內存,減小I/O操做,提升效率。
n 缺點
前綴索引不支持covering index和order by。舉例說明下:假如表account上有以下索引(balance,customer_email(50),account_number);其中字段customer_email的定義爲varchar(100),那以下的兩個SQL並不能徹底使用該索引。
select account_number
from account
where balance=100.1 and customer_email=’1@1.com’;
select account_id
from account
where balance=100.1 and customer_email=’1@1.com’ order by account_number;
前綴索引通常能夠提供高性能所需的選擇。若是索引blob和text列,或者很長的varchar列,就必須定義前綴索引,這樣既能節約空間同時能獲得好的性能。
int型的不建議使用prefix index,雖然能夠提高效率,可是卻不能使用order by, covering index等, 建議使用更小的數字類型如tinyint,bit等來知足。
前綴索引涉及索引到底建多長的選擇。短的索引能夠節約空間。可是前綴又應該足夠長,使他的選擇性可以接近索引整個列,所以前綴的基數性應該接近於全列的基數性。
設計索引的時候結合記錄數、字符集大小、字段長度、字段內容的重複程度、字符之間的相關性等考慮索引長度,索引長度不當將使索引過於龐大,內存資源利用不高, 形成IO較重,程序效率下降。合理的索引長度,能夠在知足較好索引區分度的狀況下減小索引所佔空間,咱們的目標就是找到索引空間大小與索引區分度的一個平衡點。
選擇索引長度的方法:
1) 首先了解表中記錄的整體狀況,若是表中數據還不存在或者不多,應該經過了解業務去構造和模擬符合業務和產品特色的數據,使用這些數據來模擬上線後的真實數據。
2) show table status\G;能看到avg_row_length (每行的平均長度,不許確)、rows(不許確)、data所佔空間、已有索引所佔空間等信息。
3) select count(*) from table;查看準確的整體行數。
4) 查看欲創建索引的字段的整體狀況
5) 經過select * from t procedure analyse()\G;能看到表中全部字段的min_value、max_value、min_length、max_length、是否爲null、字段平均長度、字段類型優化建議等信息。其中字段長度的相關信息很重要,它給出了字段的大體信息,對索引長度的選擇頗有幫助,而字段類型優化則是在已有內容基礎上給出的類型優化, 例如:若是你的表中有1000萬行, 字段name爲字符串, 可是卻只有」a」,」b」,」c」三個值,則會建議優化字段類型爲enum(「a」,」b」,」c」),這樣查詢和索引效率都會大大提升。
6) 查看欲創建字段的最佳索引區分度,select count(distinct city)/count(*) from city_demo;是該字段所有內容長度都作索引能達到的最理想的區分度,這個首先能夠用來衡量該字段是否適合作索引。
7) 看不一樣索引長度的區分度,這個是個平均值例如:
Select count(distinct left(city, 3))/count(*) as sel3,
Count(distinct left(city, 4))/count(*) as sel4,
Count(distinct left(city, 5))/count(*) as sel5,
Count(distinct left(city, 6))/count(*) as sel6,
Count(distinct left(city, 7))/count(*) as sel7
From city_demo;
8) 查看到city字段作3個字節索引、4個字節索引、5個字節索引、6個字節索引、7個字節索引的區分度,能夠一直增長索引長度來探測結果。
9) 若是隨着索引長度的增長,索引區分度在很明顯地增大, 那說明咱們應該繼續增長索引長度,使當咱們增長索引長度時,索引區分度沒有明顯變化,咱們仍然應該繼續增長索引長度探測。
10) 那麼探測到什麼時候爲止呢?當咱們發現繼續增長不少索引長度可是區分度卻沒有明顯提高而現有區分度接近第3條中的最佳區分度時,這個時候的索引長度可能就比較合理了。
11) 截止上面的步驟,咱們找的都是平均分佈,有可能出現的是平均區分度很好而少許數據集中出現區分度極差的狀況,因此咱們還須要查看一下區分度分佈是否均勻。
12) 查看區分度是否均勻:
select count(*) as cnt,city
from city_demo
group by city
order by cnt desc limit 100;
select count(*) as cnt,left(city,3) as pref
from city_demo
group by pref
order by cnt desc limit 100;
select count(*) as cnt,left(city,4) as pref
from city_demo
group by pref
order by cnt desc limit 100;
select count(*) as cnt,left(city,5) as pref
from city_demo
group by pref
order by cnt desc limit 100;
13) 索引選擇的最終長度應該在平均區分度(前4條)與區分度是否均勻(第5條)之間長度作一個綜合的選擇。
建完索引後show table status查看索引大小。這是一個收尾且很是重要的工做,咱們必須清楚的知道創建這個索引的代價。
5.4.3
4.1.1
4.1.2
4.1.2.1
5.1.
5.2.
5.3.
5.4.
5.4.1.
5.4.2.
5.4.3.
5.4.3.1.
5.4.3.1.1.
在create table語句中,咱們能夠指定主鍵和候選鍵。主鍵和候選鍵都有unique約束,這些列不會包含重複值。MySQL自動爲主鍵和每一個候選鍵建立一個惟一索引,以便新值的惟一性能夠很快檢查,而沒必要掃描全表。同時加速對於這些列上肯定值的查找。主鍵的索引名爲prmariy,候選鍵的名爲該鍵包含的第一列的列名。若是存在多個候選鍵的名字以同一個列名開頭,就在該列明後放置一個順序號碼區別。
通常會在表的鏈接列上創建索引,尤爲是該表頻繁參與鏈接操做。對於一個比較大的鏈接操做,若是被驅動表的鏈接列上沒有索引的話,因爲MySQL的鏈接算法是nested loop算法,會形成屢次掃描被驅動表,對數據庫形成的壓力和開銷是巨大的。
屬性列上的選擇度是指該列所包含的不重複的值和數據表中總行數(T)的比值,它的比值在1到1/T之間。選擇度越大,越適合建索引。由於對於要查找這個列上的一個值的行,經過索引能夠過濾掉大部分數據行,剩下的符合要求的行數較少,能夠快速在數據表中定位這些行。相反,若是列的選擇度比較小,經過索引過濾後的行數依然很大,和全表掃描的開銷沒有明顯的改善,甚至會更大(全表掃描帶來的是順序I/O,而經過索引過濾後的掃描多是隨機I/O)。所以,在選擇索引列時的首要條件就是候選列的選擇度。索引要創建在那些選擇度高的索引上,在選擇度低的列上儘可能避免建索引。
在不少時候,where子句中的過濾條件並非只針對某一個字段,常常會有多個字段一塊兒做爲查詢過濾條件存在於where子句中。在這時候,就須要判斷是該僅僅爲過濾性最好的(選擇度最大)的列創建索引,仍是在全部過濾條件中全部列上創建組合索引。對於這個問題,須要衡量兩種方案各自的優劣。當where子句中的這些字段組成的聯合索引過濾性遠大於其中過濾性最高的單列,就適合建聯合索引。這樣就意味着where子句中對應的每一個列過濾性都不高,可是這些單列的過濾性乘在一塊兒後過濾性就高了。
例如:要從存儲着學籍信息的表中查找來自中國,大連的女性學生,使用的SQL的where子句以下:
where country =‘china’and city =‘dalian’and gender = female;
country和city的聯合(country,city)的選擇性會比country和city各自的選擇性高,同時由於gender自己的選擇性低,將其加入對於提升整體選擇性貢獻不大,因此在此情境下適合創建(country,city)的聯合索引。
同時從性能角度講,MySQL使用聯合索引比使用index_merge算法來使用各個單列索引的效率要高,性能要好。所以對於常常一塊兒出如今where子句中的過濾條件組合,優先考慮創建這些條件列的聯合索引,而不是爲每一個單列創建索引。
索引佔用的空間越小,對於MySQL得到高性能越有益。無論是什麼類型的索引,在查詢中使用都是須要從磁盤中加載到內存中去的,不管是MyISAM對應的key cache,仍是InnoDB對應的buffer pool。這些受到程序自身和硬件條件限制都是有大小限制的,若是索引大小比較大的話,會形成這些存放索引的內存區域沒法存下整個索引數據,根據LRU算法頻繁地淘汰索引,加載新的索引進去,這就形成比較大的I/O開銷。
若是要建索引的列是很長的字符串的話,它會使索引變大。若是大小超過限制的話,能夠考慮建前綴索引,即只索引數據列中存儲的數據的前幾個字符,而不是所有的值,這樣能夠有效地減少索引的大小。固然這樣作的前提是保證索引的選擇性。在選擇列上要索引的字符長度時,考慮選擇性不能只看平均值,還要考慮最壞狀況下的選擇性。由於使用前綴索引而索引的字符數不足的話,容易形成數據分佈不均勻。若是這種狀況比較極端,可能會形成索引的做用降低。
3
4
5
5.1
5.2
5.3
5.4
5.4.1
5.4.2
5.4.3
5.4.3.1
5.4.3.2
MySQL有兩種產生排序結果的方式:使用文件排序(filesort),或者使用掃描有序的索引。explain的輸出中type列的值爲「index」,這說明MySQL會掃描索引。
MySQL能爲排序和查找行使用一樣的索引。若是可能,按照這樣一箭雙鵰的方式設計索引是個好主意。按照索引對結果進行排序,只有當order by子句中的順序和索引最左前綴順序徹底一致,而且全部列排序的方向(升序或降序)同樣才能夠。order by無需定義索引的最左前綴的一種狀況是索引中其它前導列在where子句中爲常量。若是查詢聯接了多個表,只有在order by子句的全部列引用的是第一個表才能夠。
group by實際上也一樣須要進行排序操做,並且與order by相比,group by主要只是多了排序以後的分組操做。固然,若是在分組時仍是用了其餘一些聚合函數,還須要一些聚合函數的計算。因此在group by的實現過程當中,與order by同樣能夠使用索引。同時使用索引帶來的性能提高是巨大的。
在MySQL中group by使用索引的方式有兩種:使用鬆散(loose)索引掃描;使用緊湊索引掃描。
鬆散索引掃描實現group by是指MySQL徹底利用索引掃描來實現group by時,並不須要掃描全部知足條件的索引鍵便可完成操做,得出結果。若是MySQL使用了這種方式,則在explain的extra行會出現「using index for group-by」。要利用到鬆散索引掃描實現group by,須要至少知足如下幾個條件:
a) group by條件列必須處在同一個索引的最左連續位置;
b) 在使用group by同時,只能使用max和min這兩個聚合函數;
c) 若是引用到了該索引中group by條以外的列條件,它就必須以常量形式出現。
緊湊索引掃描實現group by是指MySQL須要在掃描索引時,讀取全部知足條件的索引鍵值,而後再根據讀取到的數據來完成group by操做,已獲得相應的結果。這時的執行計劃中就不會出現「using index for group-by」。使用這種索引掃描方式要知足的條件是:group by條件列必須是索引中的列,同時索引中位於該條件列左邊的列必須以常數的形式出如今where子句中。
除了上述兩種使用索引掃描來完成group by外,還能夠使用臨時表加filesort實現。可是這種方式帶來的性能開銷比較大,通常也比較費時。因此group by最好實現方式是鬆散索引掃描,其次是緊湊索引掃描,最後是使用臨時表和filesort。
distinct實際上和group by操做很是類似,只是在group by以後的每組中只取其中一條記錄而已。因此,distinct的實現方式和group by也基本相同。一樣經過鬆散索引掃描或者緊湊索引掃描的方式實現要優於使用臨時表實現。但在使用臨時表時,MySQL僅是使用臨時表緩存數據,而不須要進行排序,也就省了filesort操做。
含有limit子句的查詢每每同時含有order by子句(若是沒有order by子句則優化方法和普通查詢同樣)。這樣的查詢最好在排序時使用索引掃描進行排序。不然即便limit子句中只取排序後起始部分不多的數據都會引發MySQL取出所有符合條件的數據進行排序。若是使用索引掃描的話,則不須要對全部數據排序,只需掃描索引取出知足limit限制的數據便可。
同時對於limit子句中的大偏移量的offset,好比limit 10000,20,它就會產生10020行數據,而且丟掉前10000行。這個操做的代價太大。一個提升效率的簡單技巧是在覆蓋索引上進行偏移,而不是對全行數據進行偏移。也能夠將從覆蓋索引上提取出來的數據和全表數據進行鏈接,而後取得須要的數據。
在MySQL中,只有一種鏈接join算法即nested loop join。該算法就是經過驅動表的結果集做爲循環的基礎數據,而後將該結果集中的數據做爲過濾條件一條條地到下一個表中查詢數據,最後合併結果。因此在經過結果集中的數據做爲過濾條件到下一個表中地位數據時,最好是經過索引,而不是掃表。由於若是結果集中的數據比較多,要是每次都經過掃描來定位的話,形成的開銷和對MySQL的壓力是巨大的。所以,最好在被驅動表的鏈接列上創建索引,而且使MySQL在鏈接過程當中使用索引。
若是在查詢中沒有隔離索引的列,MySQL一般不會使用索引。「隔離」列意味着它不是表達式的一部分,也沒有位於函數中。
例如,下面的查詢不能使用actor_id上的索引:
select account_id from account where account_id+1=5;
人們能輕易地看出where子句中的actor_id等於4,可是MySQL卻不會幫你求解方程。應該主動去簡化where子句,把被索引的列單獨放在比較運算符的一邊。
下面是另一種常見的問題:
select expenditure from consume where to_days(current_date)-to_days(consume_time) <=10;
這個查詢將會查找date_col值離今天不超過10的全部行,可是它不會使用索引,由於使用了to_days()函數。下面是一種比較好的方式:
select expenditure from consume where consume_time>= date_sub(current_date,interval 10 day) ;
這個查詢就能夠使用索引,可是還能夠改進。使用current_date將會阻止查詢緩存把結果緩存起來,能夠使用常量替換掉current_date的值:
select expenditure from consume where consume_time>= date_sub('2010-12-12',interval 10 day);
5.4.3.3
對於where子句中過濾條件涉及的屬性列大體相同的一系列SQL創建共同的索引。若是共同涉及的屬性列是多個的話,則應創建聯合索引。在肯定聯合索引應該包含這些共同涉及的屬性列中的哪些時,應該考察這些WHERE子句對於涉及這些列上的過濾條件的形式。對於那些是範圍條件對應的列,因爲B-Tree索引自己的限制,只能選取其中一個選擇度比較高的列進入聯合索引。而對於那些等值條件對應的列,原則上均可以進入聯合索引,可是須要綜合考慮聯合索引最後的大小和進入索引的列的選擇度。若是屬性列的選擇度很是低的話,把它放入索引對於聯合索引的選擇度貢獻比較小,可是會增大索引大小,引發其它開銷。因此不要把這樣的列加入到索引中去。如3.2.1.4節中提到的例子,gender列的選擇性較低,加入聯合索引對於提升聯合索引的選擇性沒有太大幫助,但卻增長了聯合索引的大小。
對於MySQL廣泛使用的B-Tree索引,索引列的順序對於SQL使用該索引相當重要。若是索引中列的順序不合理,在使用過程當中每每會使該索引沒法被使用或者經過該索引獲得的過濾能力大大減弱。
首先因爲B-Tree索引的數據結構限制,只有當SQL的where子句使用索引的最左前綴的時候,索引才能被使用、發揮做用。因此在建立索引、決定索引的順序時,應提取但願使用該索引SQL的where子句中的過濾條件,提煉出其中的最常出現的條件和其對應的屬性列。按照這些列的選擇度由高到低排列這些屬性列,按照這個順序建立這個索引。同時相關SQL的where子句中出現的過濾條件順序,以儘可能讓這些SQL能夠使用創建的索引的最左前綴。
對於聯合索引中包含的屬性列中,有一列對應在相關SQL的where子句的過濾條件是以範圍條件出現,而索引中其餘屬性列是以等於條件出現,則應該把這些等值條件對應的列放在索引的前面,把範圍條件對應的列放在索引的最後。
select account_id from consume where account_payee =72478814 andexpenditure>1.00;
爲上述SQL建立對應的聯合索引時:若是建立索引(expenditure,account_payee),因爲expenditure列上是範圍條件,因此索引(expenditure,account_payee)沒法使用徹底(只能使用索引中的expenditure部分);如建立索引(account_payee, expenditure),SQL則能夠徹底使用此索引。因此針對上述SQL應該建立聯合索引(account_payee, expenditure)。
MySQL容許你在同一列上建立多個索引,它不會注意到你的錯誤,也不會爲錯誤提供保護。MySQL不得不單獨維護每個索引,而且查詢優化器在優化查詢的時候會逐個考慮它們,這會嚴重影響性能。重複索引是類型相同,以一樣的順序在一樣的列上建立的索引。應該避免建立重複索引,而且在發現它時把它移除掉。
有時會在不經意間建立重複索引。例以下面的代碼:
create table test(
id int not null primary key,
unique(id),
index(id)
);
對於id列,首先它是primary key,同時unique(id)使MySQL自動爲id建立了名爲id的索引,最後index(id),如今給id列建立了三個索引。這一般是不須要的,除非須要爲同一列創建不一樣類型的索引,如B-Tree,fulltext等類型索引。
多餘索引和重複索引不一樣。例如列(A,B)上有索引,那麼另一個索引(A)就是多餘的。也就是說(A,B)上的索引能被當成索引(A)(這種多餘只適合B-Tree索引)。多餘索引一般發生在向表添加索引的時候,例如,有人也許會在(A,B)上添加索引,而不是對索引(A)進行擴展。
對於B-Tree類型索引,有單列索引對應的屬性列出如今了某個聯合索引的第1位置上,那麼這個單列索引多是多餘的。
若是某一索引是主鍵的超集,那麼這個索引除非有特殊理由(如但願使用覆蓋索引),不然也是多餘索引。由於主鍵是惟一索引,過濾能力很強,和它創建聯合索引意義不大。
在大部分狀況下,多餘索引都是很差的,爲了不它,應該擴展已有索引,而不是添加新的索引。可是,還有一些狀況出於性能考慮須要多餘索引。使用多餘索引的主要緣由是擴展已有索引的時候,它會變得很大。
索引是找到行的高效方式,可是MySQL也能使用索引來接收數據,這樣就能夠不用讀取行數據。包含全部知足查詢須要的數據的索引叫覆蓋索引。覆蓋索引和任何一種索引都不同。覆蓋索引必須保存它包含的列的數據。MySQL只能使用B-Tree索引來覆蓋查詢。
在SQL執行中要使用覆蓋索引的話,須要相應的索引包含SQL中where子句中涉及的列都在索引中且知足最左前綴,同時SQL的返回列也必須在索引中。同是where子句中的過濾條件中不能包含like等操做符和函數。MySQL使用了覆蓋索引,會在explain輸出中出現「using index」字樣。
有關使用覆蓋索引的案例請參見第4章4.2.3.6使用covering index優化select語句。
5.4.3.4
MySQL查詢優化器在決定如何使用索引的時候會調用兩個API,以瞭解索引如何分佈。第一個調用接受範圍結束點而且返回該範圍內記錄的數量;第二個調用返回不一樣類型的數據,包括數據基數性(每一個鍵值有多少記錄)。當存儲引擎沒有向優化器提供查詢檢查的行的精確數量的時候,優化器會使用索引統計來估計行的數量,統計能夠經過運行analyze table從新生成。MySQL的優化器基於開銷,而且主要的開銷指標是查詢會訪問多少數據。若是統計永遠沒有產生,或者過期了,優化器就會作出很差的決定。解決方案是運行analyze table。
每一個存儲引擎實現索引統計的方式不一樣,因爲運行analyze table的開銷不一樣,因此運行它的頻率也不同。
Memory存儲引擎根本就不保存索引統計。
MyISAM把索引統計保存在磁盤上,而且analyze table執行完整的索引掃描以計算基數性。整個表都會在這個過程當中被鎖住。
InnoDB不會把統計信息保存到磁盤上,同時不會時時去統計更新它們,而是在第一次打開表的時候利用採樣的方法進行估計。InnoDB上的analyze table命令就使用了採樣方法,所以InnoDB統計不夠精確,除非讓服務器運行很長的時間,不然不要手動更新它們。一樣,analyze table在InnoDB上不是阻塞性的,而且相對不那麼昂貴,所以能夠在不大影響服務器的狀況下在線更新統計。
B-Tree索引能變成碎片,它下降了性能。碎片化的索引可能會以不好或非順序的方式保存在磁盤上。同是表的數據存儲也能變得碎片化。碎片化對於數據的讀取,尤爲是範圍數據的讀取,會使讀取速度慢不少。爲了消除碎片,能夠運行optimize table解決。
5.4.3.5
索引是獨立於基礎數據以外的一部分數據。假設在table t中的column c建立了索引idx_t_c,那麼任何更新column c的操做包括插入insert,update,MySQL在更新表中column c的同時,都必須更新column c上的索引idx_t_c數據,調整由於更新帶來鍵值變化的索引信息。而若是沒有對column c創建索引,則僅僅是更新表中column c的信息就能夠了。這樣因調整索引帶來的資源消耗是更新帶來的I/O量和調整索引所致的計算量。
基於以上的分析,在更新很是頻繁地字段不適合建立索引。不少時候是經過比較同一時間內被更新的次數和利用該列做爲條件的查詢次數來判斷的,若是經過該列的查詢並很少,可能幾個小時或者更長時間纔會執行一次,更新反而比查詢更頻繁,那麼這樣的字段確定不適合建立索引。
5
6
6.1.
在select子句中使用select *會給MySQL數據庫形成比較大的資源開銷和性能影響。好比select *會形成覆蓋索引(covering index)失效,由於索引極少甚至不可能包含表中全部的屬性列;在MySQL使用filesort時,因爲select *中包含blob,text列,而致使filesort使用雙路排序算法,從而增長IO和SQL執行時間;select *自己返回了一些不須要的列,增長了無用的IO,網絡傳輸,CPU等開銷。
基於select *的缺點,在寫select子句時,應該仔細分析須要返回的屬性列,只把須要的屬性列加入select子句。對於select *要保持警戒,除非有確實的需求,通常不寫這樣的select子句。
select子句中能夠包含count(),min(),max()等聚合函數。而索引和列的可空性經常幫助MySQL優化掉這些聚合函數。好比,爲了查找一個位於B樹最左邊的列的最小值,那麼直接找索引的第一行就能夠了。若是MySQL使用了這種優化,那麼在explain中看到「select tables optimized away」。一樣地,沒有where子句的count(*)一般也會被一些存儲引擎優化掉(好比MyISAM老是保留着錶行數的精確值)。
可是對於沒有索引直接可用的min()和max()時,通常作不到很好地優化。能夠嘗試優化掉min()和max(),利用數據的有序性配合limit 1將SQL等價轉化。
應用場景:
select min(customer_id)from customer where customer_name= '張三';
由於在customer_name上沒有索引,因此查詢會掃描整個表。從理論上說,若是MySQL掃描主鍵,它應該在發現第一個匹配以後就當即中止,由於主鍵是按照升序排列的,這意味着後續的行會有較大的customer_id。可是在這個例子中,MySQL會掃描整個表。一個變通的方式就是去掉min(),而且使用limit來改寫這個查詢,以下:
select customer_id from customer use index(primary) where customer_name ='張三' limit 1;
這個通用策略在MySQL試圖掃描超過須要的行時能很好地工做。
訪問同一張表的連續幾個查詢應該引發注意,即便它們嵌在if…then…else結構中,也是如此。看下面的應用場景:它包含了兩個查詢,但只引用到了一張表。
select count(*) from consume where account_id=6111and account_payee=51906734;
select count(*) from consume where account_id=6111and account_payee=95161305;
這兩個SQL訪問同一張表(consume),掃描同一個索引(index_accountid_expenditure_consumetime)兩次。這些開銷屬於重複開銷。能夠使用匯總技術,不用依次檢查不一樣的條件了,一遍就能收集到多個結果,而後再測試這些結果,而不須要在訪問數據庫。應該彙總技術改寫合併上述兩個SQL獲得新的SQL:
select sum(case account_payee when '51906734'then 1 else 0 end) as p1_sum, sum(case account_payee when '95161305'then 1 else 0 end) as p2_sum from consume where account_id=6111;
SQL只須要掃描索引(index_accountid_expenditure_consumetime)一次及表(consume)就能得到結果,節省了很大的開銷。
6
6.1
6.1.1
6.1.2
6.1.3
6.1.4
在SQL時,應該檢查一下在from子句中出現的那些表的做用。這其中主要會出現3種類型的表:
n 從中返回數據的表,其字段可能用於也可能沒有用於where子句的過濾條件中;
n 該表中沒有要返回的數據,但在where子句中的條件使用了該表的的列;
n 僅做爲另兩種表之間的「粘合劑」而出現的表,使SQL能夠經過錶鏈接將那些1),2)類型的表連在一塊兒。
n 既沒有從該表中返回數據,也沒有過濾條件涉及該表,同時該表也沒有參加聯接。
對於第4)類型的表,屬於from子句中整個SQL未涉及的表,能夠從from子句中去掉。
對於第3)類型的表,舉例分析:表A聯接表B,聯接列是ab,同時表B聯接表C。其中表B是第3)類型的表。須要分析,A表中的ab列是否能夠爲空值:若是能夠空值,則A和B的聯接對最後的結果集是有貢獻的,不能去掉表B及A與B的聯接;反之,不能夠爲空,若是表A能夠直接聯接表C的話,能夠去掉表B,表A直接聯接表C。上述結論是基於分析:空值的值是未知的,不能說它等於任何東西,兩個空值也不相等。對於表A中的列ab能夠爲空值的狀況,若是去掉了表B及表A與它的聯接,那麼表A中那些其它屬性列的值符合過濾條件,但ab列對應值爲空值的記錄有可能進入最後的結果集。所以在這種狀況,去掉表B及相關聯接,可能改變結果集。
以下場景:
select a.account_number from consume c, cust_acct b,account a where a.account_id=b.account_id and b.account_id=c.account_id and c.expenditure>1.00;
分析SQL中表c的account_id不能夠爲空值,因此能夠去掉表b及其相關的聯接,改寫成:
select a.account_number from consume c,account a where a.account_id=c.account_id and c.expenditure>1.00;
6.1.5
MySQL獲取所須要的數據表中數據的主要方式有兩種:1經過索引得到該數據所在行的位置後取得數據(index);2經過逐行掃描數據表中的數據行來過濾出所須要的數據(full table scan)。這兩種數據獲取方式各有利弊。
經過索引獲取數據方式的利:
n 能夠經過索引迅速找到須要的數據在數據表中存放的位置,迅速定位。這種方式在經過索引定位得到的數據行數很少的狀況下,效率高;
n 一般狀況下,索引數據和數據表相比較小,所以容易被緩存。
經過索引獲取數據的弊:
若是經過索引得到的數據行數比較多,由於索引中的數據存放是有序的,而這種順序若是和數據表中存放這些數據的順序不一致,則在索引得到數據位置後從數據表中取數據會引發大量的隨機讀,這對MySQL的性能影響比較大。
經過掃描數據表得到所需數據方式的利:
n 掃描數據表時,是按照數據表中數據存放位置順序掃描,這是順序讀操做。如今的硬件設備對順序讀性能仍是不錯的;
n 同時對於如今的硬件設備和文件系統,去讀取某個block數據時,每每會將該block臨近的其餘幾個block數據一塊兒讀到內存中,而後經過virtual I/O獲取該block。這種模式對於順序掃表操做比較有利。
經過掃描數據表得到所需數據方式的弊:
若是數據表中的數據行數比較多,則掃表一次,須要耗費比較多的CPU時間和屢次的IO操做。
瞭解了經過索引獲取數據(index)和掃表獲取數據(full table scan)兩種方式各自的利弊後,在實際狀況中如何取捨,須要考慮如下因素:
n 數據表中的數據是否按照索引順序存放,如InnoDB採用聚簇索引來存放數據(按照primary key順序來存放數據)。這種狀況下,經過索引得到的數據位置信息的順序和數據存放順序一致,所以不會引發大量的隨機讀。優先考慮使用這個索引;
n 考慮經過能利用索引過濾餘下的數據行數,若是過濾後餘下行數依然很大,那意味着還須要大量的IO從數據表中得到須要的數據。若是是隨機IO,對MySQL的影響就更大了。而相比之下,順序掃表,由於硬件和文件系統的特色(一次cache多個block)的特色,而變得有優點。
n 要考慮索引文件的大小。若是索引文件太大的話,沒法在MySQL的cache中緩存下,則從索引中得到數據位置也會引發大量的IO。
select account_id from cust_acct where customer_id=1;
若是customer_id=1條件對應的account_id太多,從執行時間上看不使用索引index_customerid而使用順序掃表,效果更好;相反對於customer_id=1,使用索引index_customerid效果更好。
由於MySQL中的MyISAM,InnoDB等引擎使用的索引主要都是B-Tree數據類型,因此對where子句中的限制條件的排列順序會有一些限制。這些限制包括:
n where子句中限制條件排列最好爲對應索引的最左前綴;
n where子句中的range(範圍)條件後的限制條件沒法使用索引(即便這些條件對應的列在索引中)。
對於1)MySQL 5.0以上的MySQL優化器能夠本身調整where子句中限制條件的順序以使用對應的索引。可是這個過程自己會增長優化器的壓力,尤爲是where子句中的限制條件和對應索引包含列較多時。因此建議在寫SQL的where子句中考慮打算使用的索引中列的順序,相應地調整where子句中限制條件的順序,以知足最左前綴的限制。
對於2)能夠考慮同時調整索引中列的順序及where子句中限制條件的順序:將range(範圍)限制條件調整到where子句的最後(最右)部分;將range條件對應的列調整到索引中列順序的最後(最右)部。
select account_id from consume where expenditure>1.00 and account_payee =72478814;
如上語句,expenditure>1.00是一個範圍限制條件,能夠調整其至account_payee條件以後。同時設計索引index_accountpayee_expenditure時考慮到這個問題,索引中列順序爲(account_payee,expenditure)。
在SQL中常常會出現多個range(範圍)限制條件。這裏指的範圍限制條件包括>,<,between等。這些範圍限制條件會對相應的索引使用形成影響。正常狀況下,即便將這些範圍條件調到where子句中的最後(最右)部,第一個範圍限制條件後的範圍條件對應的索引列也都沒法在索引中同時被使用。
select account_id from consume where account_payee between51906734and51907000and expenditure>0.00;
由於account_payee between51906734and51907000這個條件,獲得的執行計劃中只能使用索引index_accountpayee_expenditure中的account_payee列,而沒法使用expenditure列。
在SQL中出現範圍限制條件後,能夠考慮這個範圍條件對應到數據時,包含的值是不是有限個(個數不是太多)。若是存在這種特性,能夠將範圍條件轉換爲多個等值條件in()。MySQL對於in()條件處理和等值條件同樣,不會影響索引中其餘列(右邊列)的使用。針對上述應用場景,能夠改成in():
select account_id from consume where account_payee in (51907000,51906734,51906740) and expenditure>0.00;
通過改寫的SQL就能夠使用索引index_accountpayee_expenditure中包含的所有列信息。
一條SQL在MySQL中執行的過程當中,會通過SQL解析,優化器制定執行計劃,存儲引擎查找過濾數據,返回客戶端等步驟。其中優化器制定計劃階段有可能成爲MySQL的性能瓶頸。優化器爲一條SQL制定執行計劃的過程相對比較複雜,其中包括語法樹優化,相關開銷信息獲取,計算和比較等。若是併發比較大,在優化器處堆積了大量的SQL須要優化的話,優化器可能成爲性能瓶頸。此時在MySQL中經過查看各個線程的狀態(show processlist),能夠看到不少線程處在statistics。
對於這種狀況,能夠檢查一下向MySQL發起的SQL是不是形式一致,只是變量不一樣。若是是這種狀況,能夠由應用層在一個時間段(應用能夠接受的)內收集這樣的請求,使用in()將不一樣變量的SQL合併成一個SQL發給MySQL。這樣MySQL本來須要制定數個執行計劃的工做,變成只須要制定一個,減輕了優化器的壓力。
應用場景:
select expenditure from consume where account_id =1;
能夠將數個上述的SQL合併成下面的SQL ,從而減輕優化器的壓力,而且不會影響到索引的使用。
select expenditure from consume where account_id in(1,2,3);
對於is null限制條件,MySQL會首先檢查is null限制條件涉及的列,若是該列聲明時是not null的,則優化器會直接忽略掉該is null限制條件。
對於能夠爲null的列上的is null限制條件,MySQL一樣能夠使用該列上的索引。使用索引的條件和等值,range限制使用索引的條件同樣。
同時須要注意的是MySQL對於一條SQL只能使用索引來處理一個is null限制條件。好比:
select * from consume where account_payee is null and expenditure is null;
上述SQL只能使用index_accountpayee_expenditure中account_payee列,索引中expenditure列數據沒法使用,這和等值限制不一樣。
index merge(索引合併)方法適用於經過多個等值條件index定位或range
掃描搜索數據行並將結果合併成一個的SQL語句。合併會產生並集,交集等。
對於下面的SQL,MySQL優化器會考慮使用index merge算法。
select id from cust_acct where customer_id=1 or account_id=1;
經過explain觀察該SQL的執行計劃,獲得MySQL同時使用了customer_id列和account_id列上的兩個索引。在這種狀況下使用index merge會大大加速SQL的執行,能夠將上面的SQL與下面的SQL進行比較。
select id from cust_acct ignore index(idx_accountid) where customer_id=1 or account_id=1;
同時須要指出若是or條件是在同一屬性列上的,MySQL則會考慮使用該屬性列上的索引,這和上面所講的or條件在不一樣屬性列上是不一樣的。(此時至關於IN)
select id from cust_acct where customer_id=1 or customer_id=2;
index merge優化對於這種or條件的並集操做比較有效。對於and條件的交集操做,其效果不如聯合索引。如:
select id from cust_acct where customer_id=1 and account_id=1;
建(customer_id,accout_id)的聯合索引,MySQL使用該聯合索引的效果要優於使用index merge的效果。
對於or條件和and條件同時存在的where子句,MySQL優化器會優先使用and子句中的索引,而放棄使用or條件的index merge。若是能肯定or條件的index merge優於and子句中的索引,能夠使用ignore index(and子句的索引)的hint技術干預MySQL優化器制定的執行計劃。
在MySQL中,order by的實現有以下兩種類型:
n 經過有序索引直接得到有序的數據,這樣不用任何排序操做便可獲得知足要求的有序數據;
n 須經過MySQL的排序算法將存儲引擎返回的數據進行排序後獲得有序的數據。
order by子句場景:
select consume_time from consume where account_payee=48370945order by expenditure;
select account_id from consume where consume_time between '2009-09-07'and '2009-09-10'order by expenditure;
第一個SQL使用索引index_accountpayee_expenditure。Order by使用索引須要知足一些條件:where子句中涉及的列加order by子句中涉及的列要知足要使用的索引的最左前綴;同時where子句中的條件必須是等值條件。
第二個SQL沒法藉助索引實現order by,須要經過MySQL自己的排序算法完成order by操做。
利用索引實現數據排序是MySQL中實現結果集排序的最佳方法,能夠徹底避免由於排序計算帶來的資源消耗。因此,在優化SQL中的order by時,儘量利用已有的索引來避免實際的排序計算,甚至能夠增長索引字段,這能夠很大幅度地提高order by操做的性能。固然這須要從總體上權衡,不能所以影響其餘SQL的性能。
在分頁等系統中使用limit和offset是很常見的,它們一般也會和order by一塊兒使用。一個比較常見的問題是偏移量很大,好比查詢使用了limit 10000,20,它就會產生10020行數據,而且丟掉前面10000行。這個操做代價比較高。
一個提升效率的簡單技巧就是在覆蓋索引上進行偏移,而不是對全行數據進行偏移。能夠將從覆蓋索引上提取的數據和全行數據進行聯接,而後取得所需的列。這會更有效率。
應用場景:
select * from consume where account_payee=72478814order by expenditure limit 50,5;
select * from consume inner join (select account_id from consume where account_payee=72478814order by expenditure limit 50,5) as lim using(account_id);
下面的SQL是上面SQL的優化版本,它使用覆蓋索引,避免了整個結果集上進行offset操做。
在前面章節已經介紹了覆蓋索引,覆蓋索引能夠使MySQL從索引中得到所需的數據,而不須要再到數據表中去取所須要的數據。一般索引要比數據表小,同時有序。所以使用覆蓋索引對提升SQL性能會有很大的幫助。
在MySQL中,覆蓋索引的使用要求select子句中涉及的列必須在相應的同一索引中,同時where子句中的限制條件應該符合使用這個索引的要求。好比限制條件要知足索引的最左前綴要求。若是有range條件,order by子句等,都要知足索引對這些條件的限制。對於那些不知足這些條件的SQL,能夠經過適當的改造,以使它能使用覆蓋索引達到優化的效果。
應用場景:
select account_id from account where account_bank= 'icbc.beijing.fengtai'and customer_email='email8002';
select * from account where account_bank= 'icbc.beijing.fengtai'and customer_email like '%@baidu.com';
由於account表使用的是InnoDB引擎,因此索引index_accountbank_customeremail存儲有account_id的信息(它是primary key)。第一個SQL能夠使用覆蓋索引;可是第二個SQL卻不能,由於select子句要求返回全部列,超出了索引index_accountid_expenditure_consumetime的範圍。能夠將SQL修改一下:
select * from account join (select account_id from account where account_bank='icbc.beijing.fengtai' and customer_email like '%@baidu.com') t on t.account_id=account.account_id;
經過上述改寫,from子句中的select account_id from account where account_bank='icbc.beijing.fengtai' and customer_email like '%@baidu.com'能夠使用覆蓋索引。這樣在account_bank='icbc.beijing.fengtai'過濾出來的數據行比較多,而加上customer_email like '%@baidu.com'條件後過濾出來的數據很少的狀況下,這種改寫會有比較大的優化效果。由於未改寫前對customer_email條件過濾須要去數據表取數據,而改寫後直接在索引中進行。同時因爲改寫後最終到數據表中取的數據較少,因此優化效果明顯。
在寫完SQL後,必定將其在數據庫上執行一遍,觀察一下輸出結果。若是輸出結果呈必定的規律性,則可考察一下數據庫中相應的數據是否都符合這種規律。若是是的話,則能夠從新審視先前的SQL,其中的限制條件等是否能夠修改,來避免其中使用函數等複雜的限制條件。這樣有助於MySQL指定執行計劃時充分使用索引,指定最優的執行計劃。一樣有助於提高SQL的執行速度。
應用場景:
select account_id from account where substr(account_bank,6,7)= 'beijing';
上述SQL因爲使用函數substr,則不能使用索引index_accountbank_customeremail。可是經過觀察SQL的輸出,發現account_bank列的數據都是以icbc.開頭,進而能夠SQL改寫以下:
select account_id from account where account_bank like 'icbc.beijing%';
由於限制條件改寫成了like 'icbc.beijing%',符合MySQL索引使用規範,該SQL就能夠使用索引index_accountbank_customeremail了。
6.2.
在介紹join語句的優化思路以前,首先要理解在MySQL中是如何實現join的。只要理解了其實現原理,優化就比較簡單了。
在MySQL中,只有一種join算法,就是nested loop join,它沒有不少其餘數據庫提供的hash Join,也沒有sort merge join。nested loop join實際上就是經過驅動表的結果集做爲循環基礎數據,而後將該結果集中的數據(聯接鍵對應的值)做爲過濾條件一條條地到下一個表中查詢數據,最後合併結果。若是還有第三個表參與join,則把前兩個表的join結果集做爲循環基礎數據,再一次經過循環查詢條件到第三個表中查詢數據,如此往復。
好比一個表t1,t2,t3的聯接,經過explain觀察到的執行計劃中join的類型(explain中type行的值)是:
table join type
t1 range
t2 ref
t3 All
則相應的這個聯接的算法僞代碼以下:
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions
send to client
}
}
}
針對MySQL這種比較簡單join算法,只能經過嵌套循環來實現。若是驅動結果集越大,所需循環也就越多,那麼被驅動表的訪問次數天然也就越多,並且每次訪問被驅動表,即便所需的IO不多,循環次數多了,總量也不可能小,並且每次循環都不能避免消耗CPU,因此CPU運算量也會跟着增長。可是不能僅以表的大小來做爲驅動表的判斷依據,假如小表過濾後所剩下的結果集比大表過濾獲得的結果集還大,結果就會在嵌套循環中帶來更多的循環次數。反之,所須要的循環次數就會更小,整體IO量和CPU運算量也會更少。因此,在優化join聯接時,最基本的原則是「小結果集驅動大結果集」,經過這個原則來減小循環次數。
以下場景:
經過explain觀察MySQL爲該SQL制定的執行計劃:
select a.expenditure, b.balance from consume a, account b where a.account_id = b.account_id and a.account_payee=13301087 and b.account_bank='icbc.beijing.fengtai';
join中選取表consume爲驅動表。
若是經過explain觀察,發現MySQL在join過程當中選取的驅動表不是很合適的話,建議最早經過索引,再經過hint技術straight_join來干預MySQL,使其按照最優的方式選取驅動表,制定最優的執行計劃。
對於nested loop join算法,內層循環是整個join執行過程當中執行次數最多的,若是每次內層循環中執行的操做能節省不多的資源,就能在整個join循環中節約不少的資源。
而被驅動表的join column能使用索引正是基於上述考慮的。只有讓被驅動表的join條件字段被索引了,才能保證循環中每次查詢都能經過索引迅速定位到所需的行,這樣能夠減小內層一次循環所消耗的資源;不然只能經過掃表等操做來找到所須要的行。
例如
select a.expenditure, b.balance from consume a, account b where a.account_id = b.account_id and a.account_payee=13301087 and b.account_bank='icbc.beijing.fengtai';
join過程當中被驅動表join條件列account_id是primary key,經過主鍵索引每次內層循環定位所需的行很是快且開銷很是小。
6.3.
insert插入一條記錄花費的時間由如下幾個因素決定:鏈接、發送查詢給服務器、解析查詢、插入記錄、插入索引和關閉鏈接。
此處沒有考慮初始化時打開數據表的開銷,由於每次運行查詢只會作一次。若是是B-tree索引,隨着索引數量的增長,插入記錄的速度以logN的比例降低。
能夠用如下幾種方法來提升插入速度:
n 若是要在同一個客戶端在同一時間內插入不少記錄,能夠使用insert語句附帶有多個values值。這種作法比使用單一值的insert語句快多了(在一些狀況下比較快)。若是是往一個非空數據表增長記錄,能夠調整變量bulk_insert_buffer_size的值使其更快。
n 對實時性要求不高狀況下,如要從不用的客戶端插入大量記錄,使用insert delayed語句也能夠提升速度。
n 想要將一個文本文件加載到數據表中,能夠使用load data infile。速度上一般是使用大量insert語句的20倍。
n 若插入上百萬,建議分批進行,批量插入3000~5000後,sleep數秒鐘以後進行下一次插入,能夠避免同步延遲的累積。
若是編寫的delete語句中沒有where子句,則全部的行都被刪除。當不想知道被刪除的行的數目時,有一個更快的方法,即便用truncate table。
若是刪除的行中包括用於auto_increment列的最大值,對於MyISAM表或InnoDB表不會被從新用。若是在autocommit模式下使用delete from tbl_name(不含where子句)刪除表中的全部行,則對於全部的表類型(除InnoDB和MyISAM外),序列從新編排。對於InnoDB表,此項操做有一些例外。
對於MyISAM和BDB表,您能夠把auto_increment次級列指定到一個多列關鍵字中。在這種狀況下,從序列的頂端被刪除的值被再次使用,甚至對於MyISAM表也如此。delete語句支持如下修飾符:
若是您指定low_priority,則delete的執行被延遲,直到沒有其它客戶端讀取本表時再執行。
在刪除行的過程當中,ignore關鍵詞會使MySQL忽略全部的錯誤。(在分析階段遇到的錯誤會以常規方式處理。)因爲使用本選項而被忽略的錯誤會做爲警告返回。
update更新查詢的優化同select查詢同樣,但須要額外的寫開銷。寫的速度依賴更新的數據大小和更新的索引的數量。因此,鎖定表,同時作多個更新比一次作一個快得多。(需注意UPDATE的規模)
另外一個提升更新速度的辦法是推遲更新而且把不少次更新放在後面一塊兒作。若是鎖表了,那麼同時作不少次更新比分別作更新來得快多了。
因爲replace是先delete再insert的操做,有可能會致使系統的空洞沒法獲得使用。因此採用先select判斷是否存在記錄,而後再考慮是否進行insert仍是update的方式進行數據的更新可能更好。能夠考慮採用insert on duplicate key,replace和insert on duplicate key差異在於前者是delete-insert,後者是update。
truncate和不帶where子句的delete,以及drop都會刪除表內的數據;
truncate和delete只刪除數據不刪除表的結構(定義);drop語句將刪除表的結構被依賴的約束(constrain),觸發器(trigger),索引(index);依賴於該表的存儲過程/函數將保留,可是變爲invalid狀態;
delete語句是DML語句,這個操做會放到rollback segement中,事務提交以後才生效,若是有相應的trigger,執行的時候將被觸發;truncate,drop是DDL語句,操做當即生效,原數據不放到rollback segment中,不能回滾.操做不觸發trigger;
在執行速度方面,通常來講: drop> truncate > delete;
可是在安全性上,當心使用drop和truncate,尤爲沒有備份的時候;
使用上,想刪除部分數據行用delete,注意帶上where子句,不然回滾段要足夠大;想刪除表,固然用drop;想保留表而將全部數據刪除,若是和事務無關,用truncate便可;若是和事務有關,或者想觸發trigger,仍是用delete.
當一個查詢語句嵌套在另外一個查詢的查詢條件之中時,稱爲子查詢。子查詢老是寫在圓括號中,能夠用在使用表達式的任何地方。子查詢也稱爲內部查詢或者內部選擇,而包含子查詢的語句也稱爲外層查詢或外層選擇。MySQL是從4.1支持子查詢功能的,以前可考慮使用聯表查詢進行替代。
根據子查詢出現的位置:位於select子句中,位於from子句中,位於where條件中的子查詢來逐個介紹。
6.4.
位於select子句的子查詢是指子查詢在外層查詢的select項。此時子查詢返回的值是n行一列的表集合(n>=1)。
應用場景:帳號名爲'a-001'的客戶信息。
select a.account_id,a.balance,
(select c.customer_name
from customer as c
where c. customer_id=ac.customer_id )as cust_name
from account as a, cust_acct as ac
where a. account_id=ac.account_id and a. account_id= 'a-001';
子查詢經過與ac.customer_id相等的條件引用了主查詢的當前記錄。並且該子查詢只能返回一條記錄,若是沒有符合條件的,則結果中的cust_name值爲空。
優化關注點:對於子查詢來講,外層查詢返回的每條記錄都會須要執行這個子查詢。若是返回幾百條或者更少記錄來講,上面語句的性能應該還不錯,可是對於返回幾百萬,上千萬行數據的查詢來講,這樣作就很是致命了。能夠替換爲外鏈接,以下:
select a.account_id,a.balance,c.customer_id
from account as a, cust_acct as ac left join customer as c
on c.customer_id = ac.customer_id
where a.account_id=ac.account_id and a.account_id= 'a-001';
注意,鏈接須要是個外鏈接,這樣才能在customer表中無對應記錄時獲得空值。
位於from中的子查詢是指子查詢落在外層查詢的from項。此時子查詢返回的值是n行n列的表集合(n>=1)。
應用場景:一次消費超過100元的同時消費是在最近一個月進行的帳號信息。
Select consume_gt100.account_id
from
(
select c2. account_id,c2. consume_time
from consumeas c2
where c2.expenditure>100
) asconsume_gt100
Where consume_gt100. consume_time>'2010-11-17';
其中子查詢和外層查詢沒有任何關聯。通常狀況下,from中的子查詢都是能夠獨立執行的。
位於whrere中的子查詢是指子查詢位於外層查詢的where條件中。這類查詢一般有三種基本的子查詢。
n 經過使用in引發的範圍查詢。
n 經過由any,some或all修改的比較運算符引入的列表上操做。
n 經過exists引入的存在測試。
如上三種子查詢與外層查詢很可能有關聯,也可能無關聯。第一種和第二種屬於無關聯的,能夠獨立執行;exists通常是和外層查詢都是有關聯的。
位於where中的子查詢暫時有個限制,在帶in或者由any,some,all的嵌套子查詢中,不能使用limit關鍵字。可是此限制可經過其餘方式來變相實現。
關聯子查詢和非關聯子查詢的兩個較大的區別以下:
n 關聯子查詢在來自外層查詢的值每次發生變化就會被觸發一次;而非關聯子查詢卻永遠只須要觸發一次。
n 關聯子查詢的話,是外層查詢在驅動執行過程。若是是無關聯查詢,那麼子查詢就有可能驅動外層查詢。
應用場景:和張三有在同一銀行開戶的帳號的客戶信息,而且按照customer_id遞減排序,取前10個客戶信息。
select c. customer_name,c.customer_id
from customer as c, cust_acct as ac, account as a
where c.customer_id=ac.customer_id and a.account_id=ac.account_id and a. account_bank in(
select a2.account_bank
from account as a2,cust_acct as ac2,customer as c2
where c2.customer_name='張三' and c2.customer_id=ac2.customer_id and ac2.account_id=a2.account_id
)
order by c.customer_id desc limit 0,10;
該SQL中的子查詢爲非關聯子查詢,該子查詢也能夠移到from中。可是因爲in()隱含了distinct,所以在from子查詢中須要顯式寫distinct。如上能夠改寫爲:
select c.customer_name,c.customer_id
from customer as c,cust_acct as ac,account as a,
(
select distinct a2.account_bank
from account as a2,cust_acct as ac2,customer as c2
where c2.customer_name='張三' and c2.customer_id=ac2.customer_id and ac2.account_id=a2.account_id
) as tr
where tr. account_bank=a. account_bank
and c.customer_id=ac.customer_id and a.account_id=ac.account_id
order by c.customer_id desc limit 0,10;
這兩種寫法的對比:
n from子句支持limit子句,在索引合適的狀況下,添加order by和limit在子查詢中可減小文件排序。
n 帶in的子查詢和聯表比起來,MySQL更喜歡聯表。尤爲是in()中子查詢爲空時,外層查詢會逐行掃描對比,而此時聯表查詢會有較大優點。
some,any和all可對子查詢中返回的多行結果進行處理,下面簡單介紹下這幾個關鍵字的含義:
some:表示知足其中一個的含義,是由or連起來的比較從句。
any:也表示知足其中一個的意義,也是用or串起來的比較從句,區別是any通常用在非「=」的比較關係中,這也很好理解,英文中的否認句中使用any,確定句中使用some,這一點是同樣的。
all:表示知足其中全部的查詢結果的含義,使用and串起來的比較從句。
應用場景:本月內消費額度超過'a-001'帳戶的全部帳戶信息。
select c.customer_name,c.customer_id
from customer as c ,cust_acct as ac, consume as c2
where c.customer_id=ac.customer_id and ac.account_id=c2.account_id
and c2.expenditure > any (select c3.expenditure
from consume as c3
where c3.account_id='a-001');
這裏改成
>some(select c3. expenditure from consume c3 where c3.account_id='a-001')
也是符合語法的,查詢結果也是一致的。
應用場景:帳戶開戶以來一直沒有消費記錄的帳戶信息。
select c.customer_name,c.customer_id
from customer as c,cust_acct as ac
where c.customer_id=ac.customer_id and not exists
(
select c2. account_id
fromconsumeas c2
where c2.account_id=ac.account_id
);
該子查詢是經過account_id進行關聯,該查詢也能夠改寫爲無關聯查詢,以下:
select c.customer_name,c.customer_id
from customer as c,cust_acct as ac
where c.customer_id=ac.customer_id and ac.account_id not in
(
select c2. account_id
fromconsumeas c2
);
在關聯子查詢和非關聯子查詢之間作選擇並非很困難,有些優化器會幫你作選擇,在使用關聯子查詢和非關聯子查詢時,對於索引狀況的假設不一樣,由於它再也不是其餘部分的查詢的那個部分了。優化器會盡量使用可用的索引,但不會建立索引。
6.5.
6.2
6.3
6.4
6.5
6.5.1
MySQL Query Optimizer經過執行explain命令告訴咱們它將使用一個怎麼樣的執行計劃來優化query。所以explain是在優化query中最直接有效的驗證咱們想法的工具。
要使用explain,只需把explain放在查詢語句的關鍵字select前面就能夠了。MySQL會在查詢裏設置一個標記,當它執行查詢時,這個標記會促使MySQL返回執行計劃裏每一步的信息。用不着真正執行。它會返回一行或多行。每行都會顯示執行計劃的每個組成部分,以及執行的次序。
MySQL只能解釋select查詢,沒法解釋存儲過程的調用、insert、update、delete和其它語句。這時只有經過重寫這些非select語句爲select,使可以被explain。
下面來詳細解釋下explain功能中展現的各類信息的解釋。
項 |
子類型 |
說明 |
id |
|
MySQL Query Optimizer選定的執行計劃中查詢的序列號。表示查詢中執行select子句或操做表的順序,id值越大優先級越高,越先被執行。id相同,執行順序由上至下。 |
select_type(所使用的查詢類型) |
dependent subquery |
子查詢內層的第一個查詢,依賴於外部查詢的結果集。 |
dependent unoin |
子查詢中的unoin,且爲union中從第二個select開始的後面全部select,一樣依賴於外部查詢的結果集。 |
|
primary |
子查詢中的最外層查詢,注意並非主鍵查詢。 |
|
simple |
除子查詢或union外的其餘查詢。 |
|
derived |
用於from子句裏有子查詢的狀況。MySQL會遞歸執行這些子查詢,把結果放在臨時表裏。 |
|
subquery |
子查詢內層查詢的第一個查詢,結果不依賴於外部查詢結果集。 |
|
uncacheable subquery |
結果集沒法緩存的子查詢。 |
|
union |
union語句中第二個select開始後面的全部select,第一個select爲primary。 |
|
union result |
union中的合併結果集。 |
|
table |
|
顯示這一步中鎖訪問的數據庫中的表名稱。 |
type(表示表的鏈接類型) |
all |
一般意味着必須掃描整張表,從頭至尾,去找匹配的行。(這裏也有例外,例如查詢中使用了limit,或者在extra列裏顯示使用了distinct或not exists等限定詞)。 |
index |
全索引掃描。與全表掃描同樣,只是掃描表的時候按照索引次序進行而不是行。主要優勢就是避免了排序;最大的缺點就是要承擔索引次序讀取整張表的開銷。這通常意味着如果隨機次序訪問行,開銷將會很是大。 |
|
range |
索引範圍掃描。就是有限制的索引掃描,返回匹配某個值域的行。這比全索引掃描好些,由於不用便利遍歷所有索引。常見於between、<、>等的查詢。 |
|
ref |
一種索引訪問方式,它返回匹配某個單獨值的全部行。可是它可能查找到多個符合條件的行,所以,它是查找和掃描的混合體。此類索引訪問只有當使用非惟一索引或者惟一性索引的非惟一性前綴纔會發生。叫作ref是由於索引要跟某個參考值相比較。這個參考值或者是一個常數,或者是來自一個表裏的多表查詢的結果值。ref_or_null是ref之上的一個變體,它意味着MySQL必須進行二次查找,在初次查找的結果裏找出null條目。 |
|
eq_ref |
惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或惟一索引掃描。 |
|
const,system |
當MySQL對查詢某部分進行優化,並轉換爲一個常量時,使用這些類型訪問。如將主鍵置於where列表中,MySQL就能將該查詢轉換爲一個常量。system是const類型的特例,當查詢的表只有一行的狀況下, 使用system。 |
|
null |
MySQL在優化過程當中分解語句,執行時甚至不用訪問表或索引。 |
|
possible_key |
|
這一列顯示查詢能夠使用的索引,若是沒有索引能夠使用,就會顯示爲null。 |
key |
|
這一列顯示的是MySQL Query Optimizer從possible_key選擇使用的索引。須要注意的是查詢中若使用了覆蓋索引,則該索引僅出如今key列表中,不會出如今possible_keys列。possible_keys說明哪個索引能有助於查詢,而key顯示的是優化器採用哪個索引能夠最小化查詢成本。 |
key_len |
|
該列顯示了使用的索引的索引鍵長度。因爲索引的最左策略,所以經過該值能夠計算查詢中使用的索引狀況。 |
ref |
|
這一列顯示上述表的鏈接匹配條件,即哪些列或常量被用於查找索引列上的值。 |
rows |
|
這一列顯示的表示MySQL根據表統計信息及索引選用狀況,估算的找到所需的記錄所須要讀取的行數。 |
filtered |
|
這一列是5.1加進去的。當使用explain extended時纔會出現。它顯示的是針對表裏符合某個條件的記錄數的百分比所做的一個悲觀估算。rows列和這個百分比相乘,就能看到MySQL估算的它將和查詢計劃裏前一個表聯接的行數 |
extra(這一列包含的是不適合在其餘列顯示的額外信息) |
using index |
該值表示MySQL將使用覆蓋索引,以免訪問表。 |
using where |
表示MySQL服務器在存儲引擎收到記錄後進行「後過濾」(post-filter),若是查詢未能使用索引,using where的做用只是提醒咱們MySQL將用where子句來過濾結果集。 |
|
using temporary |
表示MySQL在對查詢結果排序時使用臨時表。常見於排序和分組查詢。 |
|
using filesort |
表示MySQL會對結果使用一個外部索引排序,而不是從表裏按索引次序讀到相關內容。可能在內存或者磁盤上進行排序。MySQL中沒法利用索引完成的排序操做稱爲「文件排序」 |
|
range checked for each record(index map:n) |
這表示沒有好的索引可用,新的索引將在聯接的每一行上被從新評估。n是顯示在possible_keys列索引的位圖。這是一個冗餘。 |
|
explain extended和普通的explain很類似,可是它會告知服務器把執行計劃「反編譯」成select語句,而後當即執行show warnings就能看到這些生成的語句。這些語句是直接來自執行計劃,而不是原始的SQL語句。
explain只是一個近似,沒有更多的細節。有時它是一個很好的近似,可是有時它會遠離真實狀況,如下就是它的幾個侷限性:
n explain不會告訴你關於觸發器、存儲過程的信息或用戶自定義函數對查詢的影響狀況。
n explain不考慮各類cache。
n explain不能顯示MySQL在執行查詢時所做的優化工做。
n 一些顯示出來的統計信息是估算的,不是很精確。
n expalin只能解釋select操做,其餘操做要重寫爲select後查看執行計劃。
儘管優化器的做用是將很糟糕的語句轉化成高效的處理方式,但即使是在索引創建的很合理,而且優化器獲得正確的相關信息後,優化器仍是可能會制定出錯誤方向的執行計劃。通常會有兩個方面的緣由:
n 優化器的缺陷。
n 查詢太複雜,優化器只能在給它的有限時間內嘗試大量可能的組合中不多一部分優化方案,所以可能沒法找到最佳執行計劃。
針對這種狀況,最有效的解決辦法就是正確編寫查詢。以更簡單更直接的方式寫SQL一般能節省優化器不少工做,由於它能很快命中一個很好並且很高效的執行計劃。
若是不滿意MySQL優化器選擇的優化方案,能夠使用一些優化提示來控制優化器的行爲。下面簡單介紹下在MySQL中經常使用的提示,以及使用它們的時機。
6.5.2
6.5.3
1
2
3
3.1
3.2
3.3
3.4
3.5
3.5.1
3.5.2
這些提示告訴優化器在該表中查詢時使用或者忽略該索引。
force index和using index是同樣的。可是它告訴優化器,表掃描比起索引來講代價要高不少,即便索引不是很是有效。
在MySQL5.0及之前版本中,它們不會影響排序和分組使用的索引。
這個提示用於select語句中select關鍵字的後面,也能夠用於聯接語句。所以它的第一個用途是強制MySQL按照查詢中表出現的順序來聯接表,第二個用途是當它出如今兩個聯表的表中間時,強制這兩個表按照順序聯接。
straight_join在MySQL沒有選擇好的鏈接順序,或者當優化器花費很長時間肯定鏈接順序的時候頗有用。在後一種的狀況下,線程將會在「統計」狀態停留很長時間,添加這個提示將會減小優化器的搜索空間。
能夠使用explain查看優化器選擇的聯接順序,而後按照順序重寫鏈接,而且加上straight_join提示。
sql_cache代表查詢結果須要進行緩存,結果進行緩存和變量query_cache_type,have_query_cache以及query_cache_limit的設置有關。
而sql_no_cache代表查詢結果不須要進行緩存。
這兩個提示決定了訪問同一個表的SQL語句相對其它語句的優先級。
high_priority提示用於select和insert。是將一個查詢語句放在隊列的前面,而不是在隊列中等待。
low_priority提示用於select、insert、update、replace、delete、load data。和high_priority相反,若是有其餘語句訪問數據,它就把當前語句放在隊列的最後。
這兩個提示不是指在查詢上分配較多或者較少資源。它們只是影響服務器對訪問表的隊列的處理。
這個提示用於insert和update。使用了該提示的語句會當即返回而且將插入的列放在緩衝區中,在表空閒的時候在執行插入。它對於記錄日誌頗有用,對於某些須要插入大量數據,對每個語句都引起I/O操做可是又不但願客戶等待的應用程序頗有用。可是它有不少限制,好比:延遲插入不能運行於全部的存儲引擎上(僅適用於MyISAM, Memory和Archive表),而且它也沒法使用last_insert_id()。
這兩個提示用於select語句。它們會告訴MySQL在group by或distinct查詢中如何而且什麼時候使用臨時表。sql_small_result告訴優化器結果集會比較小,能夠放在索引過的臨時表中,以免對分組後的數據排序。sql_big_result的意思是結果集比較大,最好使用磁盤上的臨時表進行排序。
這個提示告訴優化器將結果存放在臨時表中,而且儘快釋放掉表鎖。
7
字符串鏈接方法,使用CONCAT()或CONCAT_WS()函數,語法以下:
CONCAT(string1,string2,...)
CONCAT_WS(separator,string1,string2,..)
字符串長度統計:
LENGTH(string) #返回string所佔的字節數
CHAR_LENGTH(string) #返回string中的字符個數
統計字符個數,就不區分是漢字仍是字母或數字,也跟字符集沒有關係,若統計的是字節數,則由字符是漢字、字母或數字類型,以及字符集共同決定。
7.1.
7.2.
n NOW()函數精確到秒,格式:YYYY-MM-DD HH:MM:SS
n CURDATE函數精確到天,格式:YYYY-MM-DD
n CURTIME函數精確到秒,格式:HH:MM:SS
n DATE_ADD(date,INTERVAL expr type)
n DATE_SUB(date,INTERVAL expr type)
經常使用的幾種type類型:YEAR、MONTH、DAY、HOUR、MINUTE,其中expr能夠爲正數或負數,咱們在開過程當中,通常使用DATE_ADD()函數,若要做日期減去一個數字的方式,就使用負數。
n DATEDIFF(expr1,expr2),是返回開始日期expr1與結束日期expr2之間,相差的天數 ,返回值爲正數或負數。
n YEAR(expr1) 返回日期expr1部分的年份;
n MONTH(expr1)返回日期expr1部分的月份;
n DAY(expr1)返回expr1部分的天數;
n WEEKDAY(expr1)返回expr1對應的星期數字;
7.3.
n DATE_FORMAT(expr1,format)
n STR_TO_DATE(expr1, format)
經常使用的日期格式YYYY-MM-DD HH:MM:SS對應的format爲%Y-%m-%d %H:%i:%S
n CAST(expr AS type)
n CONVERT(expr,type)
n CONVERT(expr USING transcoding_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|