MySQL開發規範

MySQL開發規範

基於阿里數據庫設計規範擴展而來 : https://yq.aliyun.com/articles/709387

參考,58到家MySQL軍規升級版 : https://www.jianshu.com/p/c077581693fb

基礎規範

  1. 表存儲引擎必須使用InnoDB

    MySQL常見的三種存儲引擎(storage_engine) : InnoDB、MyISAM、MEMORY

    • 存儲引擎就是指表的類型以及表在計算機上的存儲方式

    • 通過“SHOW ENGINES”語句來查看 MySQL中的存儲引擎

      特性 innoDB MyISAM Memory
      事務安全 支持
      存儲限制 64T
      空間使用
      內存使用
      插入數據的速度
      讀取數據的速度
      對外鍵的支持 支持
      全文索引 不支持 支持
    • InnoDB存儲引擎

      • InnoDB給MySQL的表提供了事務處理回滾崩潰修復能力多版本併發控制的事務安全
      • InnoDB存儲引擎總支持AUTO_INCREMENT。自動增長列的值不能爲空,並且值必須唯一。MySQL中規定自增列必須爲主鍵
      • InnoDB還支持外鍵(FOREIGN KEY)。外鍵所在的表叫做子表,外鍵所依賴(REFERENCES)的表叫做父表,父表中被子表外鍵關聯的字段必須爲主鍵。
      • InnoDB中,創建的表的表結構存儲在*.frm*文件中(我覺得是frame的縮寫吧)。數據和索引存儲在innodb_data_home_dir和innodb_data_file_path定義的表空間中
      • InnoDB的優勢在於提供了良好的事務處理、崩潰修復能力和併發控制。缺點是讀寫效率較差,佔用的數據空間相對較大。
    • MyISAM存儲引擎

      • MyISAM的表存儲成3個文件。文件的名字與表名相同。拓展名爲frmMYDMYI。其實,frm文件存儲表的結構;MYD文件存儲數據,是MYData的縮寫;MYI文件存儲索引,是MYIndex的縮寫
      • 表支持3種不同的存儲格式。包括靜態型、動態型和壓縮型。其中,靜態型是MyISAM的默認存儲格式,它的字段是固定長度的;動態型包含變長字段,記錄的長度不是固定的;壓縮型需要用到myisampack工具,佔用的磁盤空間較小。
      • MyISAM的優勢在於佔用空間小,處理速度快。缺點是不支持事務的完整性和併發性。
    • MEMORY存儲引擎

      • 表實際對應一個磁盤文件。該文件的文件名與表名相同,類型爲frm類型。該文件中只存儲表的結構。而其數據文件,都是存儲在內存中,這樣有利於數據的快速處理,提高整個表的效率。
      • MEMORY默認使用哈希索引。速度比使用B型樹索引快。當然如果你想用B型樹索引,可以在創建索引時指定。
      • MEMORY用到的很少,因爲它是把數據存到內存中,如果內存出現異常就會影響數據。如果重啓或者關機,所有數據都會消失。因此,基於MEMORY的表的生命週期很短,一般是一次性的。
    • 怎樣選擇存儲引擎

      • 同一個數據庫也可以使用多種存儲引擎的表。如果一個表要求比較高的事務處理,可以選擇InnoDB。這個數據庫中可以將查詢要求比較高的表選擇MyISAM存儲。如果該數據庫需要一個用於查詢的臨時表,可以選擇MEMORY存儲引擎。
  2. 表字符集默認使用utf8,必要時候使用utf8mb4

    character set 和 collation 的理解

    • character set (字符集) :我們常看到的UTF-8、GB2312、GB18030都是相互獨立的character set
    • collation (對比方法) : 用於指定數據集如何排序,以及字符串的比對規則
    • 每個 character set 會對應一定數量的collation ,可以通過SQL語句 show collation; 查看,collation名字的規則可以歸納爲這兩類:
      • <character set>_<language/other>_<ci/cs>
        • language/other : 同一個character set的不同 language/other 的區別在於排序、字符串對比的準確度(相同兩個字符在不同國家的語言中的排序規則可能是不同的)以及性能
        • ci 是case insensitive的縮寫,cs是case sensitive的縮寫。即指定大小寫是否敏感
      • <character set>_bin :
        • utf8_bin 是將字符串中的每一個字符用二進制數據存儲,區分大小寫

    mysql中utf8和utf8mb4區別

    • utf8 編碼最大字符長度爲 3 字節 , mb4就是most bytes 4的意思 ,
    • 應該總是使用 utf8mb4 而非 utf8. 對於 CHAR 類型數據,utf8mb4 會多消耗一些空間,根據 Mysql 官方建議,使用 VARCHAR 替代 CHAR
  3. 禁止使用存儲過程,視圖,觸發器,Event

對數據庫性能影響較大,互聯網業務,能讓站點層和服務層乾的事情,不要交到數據庫層
調試,排錯,遷移都比較困難,擴展性較差

  1. 禁止在數據庫中存儲大文件,例如照片,可以將大文件存儲在對象存儲系統,數據庫中存儲路徑
  2. 禁止在線上環境做數據庫壓力測試
  3. 測試,開發,線上數據庫環境必須隔離

設計規範

1.【推薦】字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。冗餘字段應遵循:

不是頻繁修改的字段。
不是 varchar 超長字段,更不能是 text 字段。

正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存 儲類目名稱,避免關聯查詢。

2.【推薦】單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。 說明:如果預計2年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。

3.【推薦】id必須是主鍵,每個表必須有主鍵,且保持增長趨勢的, 小型系統可以依賴於 MySQL 的自增主鍵,大型系統或者需要分庫分表時才使用內置的 ID 生成器

刪除無主鍵的表,如果是row模式的主從架構,從庫會掛住

4.【強制】id類型沒有特殊要求,必須使用bigint unsigned,禁止使用int,即使現在的數據量很小。id如果是數字類型的話,必須是8個字節。參見最後例子

方便對接外部系統,還有可能產生很多廢數據
避免廢棄數據對系統id的影響
未來分庫分表,自動生成id,一般也是8個字節

5.【推薦】字段儘量設置爲 NOT NULL, 爲字段提供默認值。 如字符型的默認值爲一個空字符值串’’;數值型默認值爲數值 0;邏輯型的默認值爲數值 0;

6.【推薦】每個字段和表必須提供清晰的註釋

7.【推薦】時間統一格式:‘YYYY-MM-DD HH:MM:SS’

8.【強制】更新數據表記錄時,必須同時更新記錄對應的 gmt_modified 字段值爲當前時間

9.【強制】禁止使用外鍵,如果要保證完整性,應由應用程式實現

外鍵使得表之間相互耦合,影響update/delete等SQL性能,有可能造成死鎖,高併發情況下容易成爲數據庫瓶頸

10.【推薦】建議將大字段,訪問頻度低的字段拆分到單獨的表中存儲,分離冷熱數據

命名規範

1.【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint ( 1表示是,0表示否)。

說明:任何字段如果爲非負數,必須是 unsigned。
正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。

2.【強制】表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只 出現數字。數據庫字段名的修改代價很大,因爲無法進行預發佈,所以字段名稱需要慎重考慮。

說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫 名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。
正例:health_user,rdc_config,level3_name 反例:HealthUser,rdcConfig,level_3_name

3.【強制】表名不使用複數名詞。

說明:表名應該僅僅表示表裏面的實體內容,不應該表示實體數量,對應於 DO 類名也是單數 形式,符合表達習慣。

4.【強制】禁用保留字,如 desc、range、match、delayed 等,請參考 MySQL 官方保留字。
5.【強制】主鍵索引名爲 pk_字段名;唯一索引名爲 uk_字段名;普通索引名則爲 idx_字段名。

說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。

6.【強制】小數類型爲 decimal,禁止使用 float 和 double。

說明:float 和 double 在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不 正確的結果。如果存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。

7.【強制】如果存儲的字符串長度幾乎相等,使用 char 定長字符串類型。
8.【強制】varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長 度大於此值,定義字段類型爲 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索 引效率。
9.【強制】表必備三字段:id, is_delete,gmt_create, gmt_modified。

說明:其中id必爲主鍵,類型爲unsigned bigint、單表時自增、步長爲1。gmt_create, gmt_modified 的類型均爲 date_time 類型,前者現在時表示主動創建,後者過去分詞表示被 動更新。

10.【強制】所有命名必須使用全名,有默認約定的除外,如果超過 30 個字符,使用縮寫,請儘量名字易懂簡短,如 description --> desc;information --> info;address --> addr 等
11.【推薦】表的命名最好是加上 業務名稱_表的作用。 正例: health_user / trade_config
12.【推薦】庫名與應用名稱儘量一致。如health
13.【推薦】如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段註釋
14.【推薦】所有時間字段,都以 gmt_開始,後面加上動詞的過去式,最後不要加上 time 單詞,例如 gmt_create

類型規範

1.表示狀態字段(0-255)的使用 TINYINT UNSINGED,禁止使用枚舉 類型,註釋必須清晰地說明每個枚舉的含義,以及是否多選等

2.表示boolean類型的都使用TINYINT(1),因爲mysql本身是沒有boolean類型的,在自動生成代碼的時候,DO對象的字段就是boolean類型,例如 is_delete;其餘所有時候都使用TINYINT(4)

TINYINT(4),這個括號裏面的數值並不是表示使用多大空間存儲,而是最大顯示寬度,並且只有字段指定zerofill時有用,沒有zerofill,(m)就是無用的,例如id BIGINT ZEROFILL NOT NULL,所以建表時就使用默認就好了,不需要加括號了,除非有特殊需求,例如TINYINT(1)代表boolean類型。

TINYINT(1),TINYINT(4)都是存儲一個字節,並不會因爲括號裏的數字改變。例如TINYINT(4)存儲22則會顯示0022,因爲最大寬度爲4,達不到的情況下用0來補充。

3.【參考】合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。

類型 字節 表示範圍
tinyint 1 無符號值: 0~255;有符號值: -128~127
smallint 2 無符號值: 0~65536;有符號值: -32768~32767
mediumint 3 無符號值: 0~16777215;有符號值: -8388608~8388607
int 4 無符號值: 0~4294967295;有符號值: -2147483648~2147483647
bigint 8 無符號值: 0~((2³²×²)-1);有符號值: -(2³²×²)/2 ~ (2³²×²)/2-1

4.非負的數字類型字段,都添加上 UNSINGED, 如可以使用 INT UNSINGED 字段存 IPV4

5.時間字段使用時間日期類型,不要使用字符串類型存儲,日期使用DATE類型,年使用YEAR類型,日期時間使用DATETIME

6.字符串VARCHAR(N), 其中 N表示字符個數,請儘量減少 N 的大小,

參考:code VARCHAR(32);name VARCHAR(32);memo VARCHAR(512);

7.Blob 和 Text 類型所存儲的數據量大,刪除和修改操作容易在數 據表裏產生大量的碎片,避免使用 Blob 或 Text 類型

索引規範

1.【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。

不要以爲唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明 顯的;另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必 然有髒數據產生。

2.【強制】超過三個表禁止 join。需要 join 的字段,數據類型必須絕對一致;多表關聯查詢時, 保證被關聯的字段需要有索引。

即使雙表 join 也要注意表索引、SQL 性能。

3.【強制】在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據 實際文本區分度決定索引長度即可。

說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度爲 20 的索引,區分度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。

4.【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

5.【推薦】如果有 order by 的場景,請注意利用索引的有序性。order by 最後的字段是組合索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢性能。

正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引中有範圍查找,那麼索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無法排序。

6.【推薦】利用覆蓋索引來進行查詢操作,避免回表。

說明:如果一本書需要知道第11章是什麼標題,會翻開第11章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是 起到覆蓋索引的作用。

正例:能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的效果,用explain的結果,extra列會出現:using index。

7.【推薦】利用延遲關聯或者子查詢優化超多分頁場景。

說明:MySQL並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行 ,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。

正例:先快速定位需要獲取的 id 段,然後再關聯:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

8.【推薦】SQL 性能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。

說明:

  • consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數據。
  • ref 指的是使用普通的索引(normal index)。
  • range 對索引進行範圍檢索。

反例:explain 表的結果,type=index,索引物理文件全掃描,速度非常慢,這個 index 級 別比較 range 還低,與全表掃描是小巫見大巫。

9.【推薦】建組合索引的時候,區分度最高的在最左邊。

正例:如果 where a=? and b=? ,a 列的幾乎接近於唯一值,那麼只需要單建 idx_a 索引 即 可。

說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:where a>? and b=? 那麼即使 a 的區分度更高,也必須把 b 放在索引的最前列。

10【推薦】防止因字段類型不同造成的隱式轉換,導致索引失效。

11.【參考】創建索引時避免有如下極端誤解

寧濫勿缺。認爲一個查詢就需要建一個索引。
寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。
抵制惟一索引。認爲業務的惟一性一律需要在應用層通過“先查後插”方式解決。

  1. 單張表索引數量建議控制在5個以內

    互聯網高併發業務,太多索引會影響寫性能

    生成執行計劃時,如果索引太多,會降低性能,並可能導致MySQL選擇不到最優索引

    異常複雜的查詢需求,可以選擇ES等更爲適合的方式存儲

  2. 組合索引字段數不建議超過5個

    如果5個字段還不能極大縮小row範圍,八成是設計有問題

    多列組合索引,過濾性高的字段最前

  3. 總結

  • 索引佔磁盤空間,不要重複的索引,儘量短  
    • 只給常用的查詢條件加索引  
    • 過濾性高的列建索引,取值範圍固定的列不建索引 
    • 唯一的記錄添加唯一索引  
    • 頻繁更新的列不要建索引  
    • 不要對索引列運算  
    • 同樣過濾效果下,保持索引長度最小  
    • 合理利用組合索引,注意索引字段先後順序  
    • order by 字段建立索引,避免 filesort  
    • 組合索引,不同的排序順序不能使用索引  
    • <>!=無法使用索引

SQL規範

1.【強制】不要使用 count(列名)或 count(常量)來替代 count(),count()是 SQL92 定義的 標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。

count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行。

2.【強制】count(distinct col) 計算該列除 NULL 之外的不重複行數,

count(distinct col1, col2) 如果其中一列全爲NULL,那麼即使另一列有不同的值,也返回爲0。

3.【強制】當某一列col的值全是 NULL 時,count(col)的返回結果爲 0,但 sum(col)的返回結果爲 NULL,因此使用 sum()時需注意 NPE 問題。

正例:可以使用如下方式來避免sum的NPE問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

4.【強制】使用 ISNULL()來判斷是否爲 NULL 值。

說明:NULL 與任何值的直接比較都爲 NULL。
NULL<>NULL的返回結果是NULL,而不是false。
NULL=NULL的返回結果是NULL,而不是true。
NULL<>1的返回結果是NULL,而不是true。

5.【強制】 在代碼中寫分頁查詢邏輯時,若 count 爲 0 應直接返回,避免執行後面的分頁語句。

6.【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

說明:以學生和成績的關係爲例,學生表中的 student_id 是主鍵,那麼成績表中的 student_id 則爲外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即爲 級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻 塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。

7.【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。

8.【強制】數據訂正時,刪除和修改記錄時,要先 select,避免出現誤刪除,確認無誤才能執行更新語句。

9.【推薦】in操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。

10.【參考】如果有全球化需要,所有的字符存儲與表示,均以 utf-8 編碼,注意字符統計函數 的區別。

說明:
SELECT LENGTH(“輕鬆工作”); 返回爲12
SELECT CHARACTER_LENGTH(“輕鬆工作”); 返回爲4 如果需要存儲表情,那麼選擇 utfmb4 來進行存儲,注意它與 utf-8 編碼的區別。

11.【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但 TRUNCATE 無事務且不觸發trigger,有可能造成事故,故不建議在開發代碼中使用此語句。

說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。

12.【推薦】不要寫一個大而全的數據更新接口。傳入爲 POJO 類,不管是不是自己的目標更新字 段,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行 SQL 時,不要更新無改動的字段,一是易出錯;二是效率低;三是增加 binlog 存儲。

  1. 【強制】避免直接使用 select *,只取需要的字段,增加使用覆蓋索引使用的可能

  2. 【強制】insert必須指定字段,禁止使用insert into T values()

    解讀:指定字段插入,在表結構變更時,能保證對應用程序無影響

  3. 【強制】禁止負向查詢以及%開頭的模糊查詢

    針對索引字段使用 >, >=, =, <, <=, IF NULL 和 BETWEEN 將會使用索引,如果對某個索引字段進行 LIKE 查詢,使用 LIKE ‘%abc%’ 不能使用索引,使用 LIKE ‘abc%’ 將能夠使用索引

  4. 【強制】禁止在where條件列使用函數或者表達式

    如果在 SQL 裏使用了 MySQL部分自帶函數,索引將失效

    能夠快速縮小結果集的 WHERE 條件寫在前面,如果有恆量條件,也儘量放在前面 ,例如 where 1=1

17.總結

- 避免使用 GROUP BY、DISTINCT 等語句的使用,避免聯表查詢和子查詢 
• 能夠使用索引的字段儘量進行有效的合理排列
• 對於大數據量的查詢,儘量避免在 SQL 語句中使用 order by 字句 
• 連表查詢的情況下,要確保關聯條件的數據類型一致,避免嵌套子查詢  
• 對於連續的數值,使用 between 代替 in  
• where 語句中儘量不要使用 CASE 條件  
• 當只要一行數據時使用 LIMIT 1

例子

CREATE TABLE `health_package` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '序號',
  `package_id` int unsigned NOT NULL COMMENT '套系 id',
  `module_id`  int unsigned NOT NULL COMMENT '模塊 id',
  `is_delete` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否刪除,0-未刪除,1-刪除,默認爲0',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time, common column by DB rules',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modified time,common column by DB rules ',
  PRIMARY KEY (`id`)
) COMMENT='This table stores module and package of health for ...';

SQL調優

  • 金字塔原理

  • 慢查詢

    -- 查看本機 MySQL 慢查詢是否打開
    show variables like 'slow%'; 
    -- 開啓慢查詢
    set global slow_query_log = on
    -- 設置存儲文檔
    set global slow_query_log_file = '/var/lib/mysql/mysql_slow_query.log'
    -- 查詢時間超過多長時間會進入慢查詢日誌: 
    set global long_query_time = 2 (單位秒)
    -- 慢查詢日誌分析工具
    mysqldumpslow -t 10 -s at /var/lib/mysql/mysql_slow_query.log
                                    
    -- 還可以使用 pt_query_digest 工具
    
  • MySQL explain

    • explain顯示了mysql如何使用索引來處理select語句以及連接表。可以幫助選擇更好的索引和寫出更優化的查詢語句
    • 如: EXPLAIN SELECT s.uid,s.username,s.name,f.email,f.mobile,f.phone,f.postalcode,f.address FROM uchome_space AS s,uchome_spacefield AS f
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章