MySQL 那些常見的錯誤設計規範

依託於互聯網的發達,咱們能夠隨時隨地利用一些等車或坐地鐵的碎片時間學習以及瞭解資訊。同時發達的互聯網也方便人們可以快速分享本身的知識,與相同愛好和需求的朋友們一塊兒共同討論。html

可是過於方便的分享也讓知識變得五花八門,很容易讓人接收到錯誤的信息。這些錯誤最多的都是由於技術發展迅速,並且沒有空閒時間去及時更新已經發布的內容所致使。爲了不給後面學習的人形成誤解,咱們今天來看一看 MySQL 設計規範中幾個常見的錯誤例子。sql

主鍵的設計

錯誤的設計規範:主鍵建議使用自增 ID 值,不要使用 UUID,MD5,HASH,字符串做爲主鍵數據庫

這個設計規範在不少文章中都能看到,自增主鍵的優勢有佔用空間小,有序,使用起來簡單等優勢。編程

下面先來看看自增主鍵的缺點:segmentfault

  • 自增值因爲在服務器端產生,須要有一把自增的 AI 鎖保護,若這時有大量的插入請求,就可能存在自增引發的性能瓶頸,因此存在併發性能問題;
  • 自增值作主鍵,只能在當前實例中保證惟一,不能保證全局惟一,這就致使沒法在分佈式架構中使用;
  • 公開數據值,容易引起安全問題,若是咱們的商品 ID 是自增主鍵的話,用戶能夠經過修改 ID 值來獲取商品,嚴重的狀況下能夠知道咱們數據庫中一共存了多少商品。
  • MGR(MySQL Group Replication) 可能引發的性能問題;

由於自增值是在 MySQL 服務端產生的值,須要有一把自增的 AI 鎖保護,若這時有大量的插入請求,就可能存在自增引發的性能瓶頸。好比在 MySQL 數據庫中,參數 innodb_autoinc_lock_mode 用於控制自增鎖持有的時間。雖然,咱們能夠調整參數 innodb_autoinc_lock_mode 得到自增的最大性能,可是因爲其還存在其它問題。所以,在併發場景中,更推薦 UUID 作主鍵或業務自定義生成主鍵。安全

咱們能夠直接在 MySQ L使用 UUID() 函數來獲取 UUID 的值。服務器

MySQL> select UUID();
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| 23ebaa88-ce89-11eb-b431-0242ac110002 |
+--------------------------------------+
1 row in set (0.00 sec)

須要特別注意的是,在存儲時間時,UUID 是根據時間位逆序存儲, 也就是低時間低位存放在最前面,高時間位在最後,即 UUID 的前 4 個字節會隨着時間的變化而不斷「隨機」變化,並不是單調遞增。而非隨機值在插入時會產生離散 IO,從而產生性能瓶頸。這也是 UUID 對比自增值最大的弊端。架構

爲了解決這個問題,MySQL 8.0 推出了函數 UUID_TO_BIN,它能夠把 UUID 字符串:併發

  • 經過參數將時間高位放在最前,解決了 UUID 插入時亂序問題;
  • 去掉了無用的字符串"-",精簡存儲空間;
  • 將字符串其轉換爲二進制值存儲,空間最終從以前的 36 個字節縮短爲了 16 字節。

下面咱們將以前的 UUID 字符串 23ebaa88-ce89-11eb-b431-0242ac110002 經過函數 UUID_TO_BIN 進行轉換,獲得二進制值以下所示:框架

MySQL> SELECT UUID_TO_BIN('23ebaa88-ce89-11eb-b431-0242ac110002',TRUE) as UUID_BIN;
+------------------------------------+
| UUID_BIN                           |
+------------------------------------+
| 0x11EBCE8923EBAA88B4310242AC110002 |
+------------------------------------+
1 row in set (0.01 sec)

除此以外,MySQL 8.0 也提供了函數 BIN_TO_UUID,支持將二進制值反轉爲 UUID 字符串。

雖然 MySQL 8.0 版本以前沒有函數 UUID_TO_BIN/BIN_TO_UUID,仍是能夠經過自定義函數的方式解決。應用層的話能夠根據本身的編程語言編寫相應的函數。

固然,不少同窗也擔憂 UUID 的性能和存儲佔用的空間問題,這裏我也作了相關的插入性能測試,結果以下表所示:

能夠看到,MySQL 8.0 提供的排序 UUID 性能最好,甚至比自增 ID 還要好。此外,因爲 UUID_TO_BIN 轉換爲的結果是16 字節,僅比自增 ID 增長 8 個字節,最後存儲佔用的空間也僅比自增大了 3G。

並且因爲 UUID 能保證全局惟一,所以使用 UUID 的收益遠遠大於自增 ID。可能你已經習慣了用自增作主鍵,可是在併發場景下,更推薦 UUID 這樣的全局惟一值作主鍵。

固然了,UUID雖好,可是在分佈式場景下,主鍵還須要加入一些額外的信息,這樣才能保證後續二級索引的查詢效率,推薦根據業務自定義生成主鍵。可是在併發量和數據量沒那麼大的狀況下,仍是推薦使用自增 UUID 的。你們更不要覺得 UUID 不能當主鍵了。

金融字段的設計

錯誤的設計規範:同財務相關的金額類數據必須使用 decimal 類型 因爲 float 和 double 都是非精準的浮點數類型,而 decimal 是精準的浮點數類型。因此通常在設計用戶餘額,商品價格等金融類字段通常都是使用 decimal 類型,能夠精確到分。

可是在海量互聯網業務的設計標準中,並不推薦用 DECIMAL 類型,而是更推薦將 DECIMAL 轉化爲整型類型。 也就是說,金融類型更推薦使用用分單位存儲,而不是用元單位存儲。如1元在數據庫中用整型類型 100 存儲。

下面是 bigint 類型的優勢:

  • decimal 是經過二進制實現的一種編碼方式,計算效率不如 bigint
  • 使用 bigint 的話,字段是定長字段,存儲高效,而 decimal 根據定義的寬度決定,在數據設計中,定長存儲性能更好
  • 使用 bigint 存儲分爲單位的金額,也能夠存儲千兆級別的金額,徹底夠用

枚舉字段的使用

錯誤的設計規範:避免使用 ENUM 類型

在之前開發項目中,遇到用戶性別,商品是否上架,評論是否隱藏等字段的時候,都是簡單的將字段設計爲 tinyint,而後在字段裏備註 0 爲何狀態,1 爲何狀態。

這樣設計的問題也比較明顯:

  • 表達不清:這個表多是其餘同事設計的,你印象不是特別深的話,每次都須要去看字段註釋,甚至有時候在編碼的時候須要去數據庫確認字段含義
  • 髒數據:雖然在應用層能夠經過代碼限制插入的數值,可是仍是能夠經過sql和可視化工具修改值

這種固定選項值的字段,推薦使用 ENUM 枚舉字符串類型,外加 SQL_MODE 的嚴格模式

在MySQL 8.0.16 之後的版本,能夠直接使用check約束機制,不須要使用enum枚舉字段類型

並且咱們通常在定義枚舉值的時候使用"Y","N"等單個字符,並不會佔用不少空間。可是若是選項值不固定的狀況,隨着業務發展可能會增長,纔不推薦使用枚舉字段。

索引個數限制

錯誤的設計規範:限制每張表上的索引數量,一張表的索引不能超過 5 個

MySQL 單表的索引沒有個數限制,業務查詢有具體須要,建立便可,不要迷信個數限制

子查詢的使用

錯誤的設計規範:避免使用子查詢

其實這個規範對老版本的 MySQL 來講是對的,由於以前版本的 MySQL 數據庫對子查詢優化有限,因此不少 OLTP 業務場合下,咱們都要求在線業務儘量不用子查詢。

然而,MySQL 8.0 版本中,子查詢的優化獲得大幅提高,因此在新版本的MySQL中能夠放心的使用子查詢。

子查詢相比 JOIN 更易於人類理解,好比咱們如今想查看2020年沒有發過文章的同窗的數量

SELECT COUNT(*)
FROM user
WHERE id not in (
    SELECT user_id
    from blog
    where publish_time >= "2020-01-01" AND  publish_time <= "2020-12-31"
)

能夠看到,子查詢的邏輯很是清晰:經過 not IN 查詢文章表的用戶有哪些。

若是用 left join 寫

SELECT count(*)
FROM user LEFT JOIN blog
ON user.id = blog.user_id and blog.publish_time >= "2020-01-01" and blog.publish_time <= "2020-12-31"
where blog.user_id is NULL;

能夠發現,雖然 LEFT JOIN 也能完成上述需求,但不容易理解。

咱們使用 explain查看兩條 sql 的執行計劃,發現都是同樣的

經過上圖能夠很明顯看到,不管是子查詢仍是 LEFT JOIN,最終都被轉換成了left hash Join,因此上述兩條 SQL 的執行時間是同樣的。即,在 MySQL 8.0 中,優化器會自動地將 IN 子查詢優化,優化爲最佳的 JOIN 執行計劃,這樣一來,會顯著的提高性能。

總結

閱讀完前面的內容相信你們對 MySQL 已經有了新的認知,這些常見的錯誤能夠總結爲如下幾點:

  • UUID 也能夠當主鍵,自增 UUID 比自增主鍵性能更好,多佔用的空間也可忽略不計
  • 金融字段除了 decimal,也能夠試試 bigint,存儲分爲單位的數據
  • 對於固定選項值的字段,MySQL8 之前推薦使用枚舉字段,MySQL8 之後使用check函數約束,不要使用 0,1,2 表示
  • 一張表的索引個數並無限制不能超過5個,能夠根據業務狀況添加和刪除
  • MySQL8 對子查詢有了優化,能夠放心使用。

推薦閱讀

實操筆記:爲 NSQ 配置監控服務的心路歷程

go-zero:開箱即用的微服務框架

相關文章
相關標籤/搜索