原文傳送門:https://blog.csdn.net/tian330726/article/details/88713086
一、基礎規範
二、命名規範
三、字段設計規範
四、索引設計規範
五、SQL查詢規範
一、基礎規範
- 使用InnoDB 存儲引擎
沒有特殊要求(即Innodb無法滿足的功能如:列存儲,存儲空間數據等)的情況下,所有表必須使用Innodb存儲引擎(mysql5.5之前默認使用Myisam,5.6以後默認的爲Innodb)Innodb 支持事務,支持行級鎖,更好的恢復性,高併發下性能更好
- 表字符集使用utf8mb4
默認使用 utf8mb4 字符集,數據庫排序規則使用 utf8mb4_general_ci,採用 utf8 編碼的 MySQL 無法保存佔位是 4 個字節的 Emoji 表情。爲了使後端的項目全面支持客戶端輸入的 Emoji 表情,升級編碼爲 utf8mb4 是最佳解決方案;
兼容性更好,統一字符集可以避免由於字符集轉換產生的亂碼,不同的字符集進行比較前需要進行轉換會造成索引失效
- 所有表都需要添加註釋
使用comment從句添加表和列的備註 從一開始就進行數據字典的維護
- 單表數據量建議控制在500萬以內
500萬並不是MySQL數據庫的限制,過大會造成修改表結構,備份,恢復都會有很大的問題,可以用歷史數據歸檔(應用於日誌數據),分庫分表(應用於業務數據)等手段來控制數據量大小
- 不在數據庫中存儲圖、文件等大的二進制數據
通常文件很大,會短時間內造成數據量快速增長,數據庫進行數據庫讀取時,通常會進行大量的隨機IO操作,文件很大時,IO操作很耗時 通常存儲於文件服務器,數據庫只存儲文件地址信息
- 儘量做到冷熱數據分離,減小表的寬度
MySQL限制每個表最多存儲4096列,並且每一行數據的大小不能超過65535字節 減少磁盤IO,保證熱數據的內存緩存命中率(表越寬,把表裝載進內存緩衝池時所佔用的內存也就越大,也會消耗更多的IO) 更有效的利用緩存,避免讀入無用的冷數據 經常一起使用的列放到一個表中(避免更多的關聯操作)
- 禁止在線上做數據庫壓力測試
- 禁止測試、開發環境直連數據庫
二、命名規範
- MySQL有配置參數lower_case_table_names=1,即庫表名以小寫存儲,大小寫不敏感。如果是0,則庫表名以實際情況存儲,大小寫敏感;如果是2,以實際情況存儲,但以小寫比較
- 如果大小寫混合使用,可能存在abc,Abc,ABC等多個表共存,容易導致混亂
- 字段名顯示區分大小寫,但實際使用時不區分,即不可以建立兩個名字一樣但大小寫不一樣的字段
- 爲了統一規範, 庫名、表名、字段名使用小寫字母,不允許
-
號
2、庫名、表名、字段名禁止超過32個字符,需見名知意,建議使用名詞不是動詞
庫名、表名、字段名支持最多64個字符,但爲了統一規範、易於辨識以及減少傳輸量,禁止超過32個字符
3、庫名、表名、字段名禁止使用MySQL保留字
當庫名、表名、字段名等屬性含有保留字時,SQL語句必須用反引號引用屬性名稱,這將使得SQL語句書寫、SHELL腳本中變量的轉義等變得非常複雜。
形如:tmp_user_account_20190313
形如:bak_user_account_20190313
6、主鍵索引名爲 pk_ 字段名;唯一索引名爲 uk _ 字段名;普通索引名則爲 idx_ 字段名
pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱
7、在不同的庫或表中,要保證所有存儲相同數據的列名和列類型必須一致
一般作爲關聯列,如果查詢時關聯列類型不一致會自動進行數據類型隱式轉換,會造成列上的索引失效,導致查詢效率降低
正例:user_task / force_project / trade_config
三、字段設計規範
列的字段類型越大,建立索引佔據的空間就越大,導致一個頁中的索引越少,造成IO次數增加,影響性能
- 業務中選擇性很少的狀態
status
、類型type
等字段推薦使用tinytint
或者smallint
類型節省存儲空間 - 能用
int
的就不用char
或者varchar
- 能用
tinyint
的就不用int
- 使用 UNSIGNED 存儲非負數值
- 使用
tinyint
來代替enum
和boolean
- 存儲 ip 最好用
int
存儲而非char(15)
通過MySQL函數inet_ntoa和inet_aton來進行轉化。IPv6地址目前沒有轉化函數,需要使用DECIMAL或兩個BIGINT來存儲
SELECT INET_ATON('209.207.224.40'); 3520061480
SELECT INET_NTOA(3520061480); 209.207.224.40
- 表中的自增列(
auto_increment
屬性),推薦使用bigint
類型
blob
,text
是爲了存儲極大的字符串而設計的數據類型,採用二進制與字符串方式存儲,該數據類型不能設置默認值、不便於排序、不便於建立索引, varchar
的性能會比 text
高很多,如果非要使用,建議將這種數據分離到單獨的拓展表中
- 無法使用日期函數計算比較
- 字符串存儲要佔更多的內存空間,
datetime
(8字節)和timestamp
(本身是以int存儲,佔4字節,範圍:1970-01-01 00:00:01到2038-01-19 03:14:07) TIMESTAMP
記錄經常變化的更新/創建/發佈/日誌時間等,並且是近來的時間,夠用,可免時區處理DATETIME
記錄生日、紀念事件、超出TIMESTAMP
的時間,記得時區處理
4、用 DECIMAL
代替 FLOAT
和 DOUBLE
存儲精確浮點數
Decimal
類型爲精準浮點數,float
和 double
在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。如果存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。
- NULL的列使用索引,索引統計,值都更加複雜,MySQL更難優化
- NULL需要更多的存儲空間
- NULL只能採用IS NULL或者IS NOT NULL,而在=/!=/in/not in時有大坑
- 牽扯到國家代號,可能出現+/-/()等字符,例如+86
- 手機號不會用來做數學運算
- varchar可以模糊查詢,例如like ‘138%’
- 字段長度固定,或者長度近似的業務場景,適合使用char,能夠減少碎片,查詢性能高
- 字段長度相差較大,或者更新較少的業務場景,適合使用varchar,能夠減少空間
建議在應用層實現外鍵的邏輯, 外鍵與級聯更新不適合高併發場景,降低插入性能,大併發下容易產生死鎖
10、整形定義中不添加長度,比如使用INT,而不是INT[4]
值類型括號後面的數字只是表示寬度而跟存儲範圍沒有關係
核心表(如用戶表,金錢相關的表)必須有行數據的創建時間字段create_time
和最後更新時間字段update_time
,便於查問題
四、索引設計規範
索引其實就是一種數據結構,(哈希表、樹等等)不同類型的索引有着不同的數據結構和功能。
MySQL的查詢速度依賴良好的索引設計,因此索引對於高性能至關重要。合理的索引會加快查詢速度,不合理的索引會降低速度
- 加速查詢速度
- 維護數據的約束性(完整性、一致性)
對於加速查詢,使用索引不一定是最好的選擇。小表就直接全表掃描,中到大表就建索引,超大表就分區分表。其實主要就要索引帶來的好處和維護索引的成本之間的權衡。
1、單表的索引數建議不超過 5 個,單個索引中的字段數建議不超過 5 個
太多就起不到過濾作用了,索引也佔空間,管理起來也耗資源
不要索引blob/text等字段,不要索引大型字段,這樣做會讓索引佔用太多的存儲空間
前綴索引就是對文本的前幾個字符建立索引,前綴索引能有效減小索引文件的大小,提高索引的速度。但是前綴索引也有它的壞處:MySQL 不能在 ORDER BY 或 GROUP BY 中使用前綴索引,也不能把它們用作覆蓋索引(Covering Index)
- 表必須有主鍵
- 不使用更新頻繁的列
- 儘量不選擇字符串列
- 不使用UUID MD5 HASH
- 默認使用非空的唯一鍵
- 建議選擇自增或發號器
- UPDATE、DELETE 語句的 WHERE 條件列
- ORDER BY、GROUP BY、DISTINCT 的字段
- 多表 JOIN 的字段
- 覆蓋索引可以避免Innodb表進行索引的二次查詢,把隨機IO變成順序IO,加快查詢效率
- 選擇篩選性更優的字段放在最前面,比如單號、userid等,type,status等篩選性一般不建議放在最前面
- 索引根據左前綴原則,當建立一個聯合索引(a,b,c),則查詢條件裏面只有包含(a)或(a,b)或(a,b,c)的時候才能走索引,(a,c)作爲條件的時候只能使用到a列索引,所以這個時候要確定a的返回列一定不能太多,不然語句設計就不合理,(b,c)則不能走索引
6、業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引
不要以爲唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明
顯的; 另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生
7、InnoDB 和 MyISAM 存儲引擎表,索引類型必須爲BTREE
MEMORY表可以根據需要選擇 HASH
或者 BTREE
類型索引
- MYISAM 存儲引擎索引長度的總和不能超過 1000 字節
- BLOB 和 TEXT 類型的列只能創建前綴索引
- 使用不等於 (!= 或者 <>) 的時候, MYSQL 無法使用索引
- 過濾字段使用函數運算 (如 abs (column)) 後, MYSQL無法使用索引
- join語句中join條件字段類型不一致的時候MYSQL無法使用索引
- 使用 LIKE 操作的時候如果條件以通配符開始 (如 ‘%abc…’)時, MYSQL無法使用索引。
- 使用非等值查詢的時候, MYSQL 無法使用 Hash 索引
五、SQL查詢規範
- 無法索引覆蓋,回表操作,增加 io
- 額外的內存負擔,大量冷數據灌入
innodb_buffer_pool_size
,降低查詢命中率 - 額外的網絡傳輸開銷
在保證數據不會有誤的前提下,能確定結果集數量時,多使用limit,儘快的返回結果。
3、涉及到複雜sql時,務必先參考已有索引設計,先explain
- 簡單SQL拆分,不以代碼處理複雜爲由
- 比如 OR 條件: f_phone=’10000’ or f_mobile=’10000’,兩個字段各自有索引,但只能用到其中一個。可以拆分成2個sql,或者union all
- 先explain的好處是可以爲了利用索引,增加更多查詢限制條件
in 的值不要超過 500 個, in 操作可以更有效的利用索引,or 大多數情況下很少能利用到索引
- 會把表中所有符合條件的數據裝載到內存中,然後在內存中對所有數據根據隨機生成的值進行排序,並且可能會對每一行都生成一個隨機值,如果滿足條件的數據集非常大,就會消耗大量的 CPU 和 IO 及內存資源
- 推薦在程序中獲取一個隨機值,然後從數據庫中獲取數據的方式
對列進行函數轉換或計算時會導致無法使用索引
不推薦:where date(create_time)='20190101'
推薦:where create_time >= '20190101' and create_time < '20190102'
7、不要使用 count(列名)或 count(常量)來替代 count(*)
count(*)是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關
count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行
以學生和成績的關係爲例,學生表中的 student_id是主鍵,那麼成績表中的 student_id則爲外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新, 即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣; 級聯更新是強阻塞,存在數據庫更新風暴的風險; 外鍵影響數據庫的插入速度
若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內
需要 join 的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引;即使雙表 join 也要注意表索引、 SQL 性能
11、SELECT語句不要使用UNION
,推薦使用UNION ALL
UNION
子句個數限制在5個以內。因爲union all
不需要去重,節省數據庫資源,提高性能
不推薦 SELECT * FROM table ORDER BY TIME DESC LIMIT 10000,10;
原因:會導致大量的io,因爲MySQL使用的是提前讀取策略
推薦:SELECT * FROM table WHERE TIME < last_TIME ORDER BY TIME DESC LIMIT 10.
SELECT * FROM table inner JOIN (SELECT id FROM table ORDER BY TIME LIMIT 10000,10) as t USING(id)
MySQL分頁查詢的性能優化 — 詳細說明