MySQL CREATE TABLE 簡單設計模板交流
阿新 • • 發佈:2022-03-21
推薦用 MySQL 8.0 (2018/4/19 釋出, 開發者說同比 5.7 快 2 倍) 或同類型以上版本.
CREATE TABLE TEMPLATE
CREATE TABLE [table_name] ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主鍵', update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '這個模板基本可以應對非跨國大部分業務場景. 後面我們也會分析小部分複雜場景. 首先來分析 CREATRE TABLE 模板設計的潛在考量. 分析 1: 物理主鍵 id 為什麼是 bigint unsigned ? 交流 :建立時間', [delete_time TIMESTAMP DEFAULT NULL COMMENT '刪除時間',] [template] ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '模板表';
- 效能更好, unsigned 不涉及 反碼和補碼 轉碼消耗
- 表示物理主鍵更廣 [-2^63, 2^63-1] -> [0, 2^64-1]
- mysql 優化會細微好點. select * from * where id < 250; 原先是 select * from * where -2^63 <= id and id < 250; 現在是 select * from * where 0 <= id and id < 250;
- 如果有些語言中沒有 unsigned, 需要把關人備註為 signed 使用範圍是 [0, 2^63-1]
- 如果 int 也能滿足業務, 也可以用 int, 節省 4 位元組. 看業務把控和取捨.
-
對於缺少 COMMENT 詳細註釋的, 推薦把關人 或
- 修改和補充 COMMENT
-- 修改表註釋 ALTER TABLE [table_name] COMMENT '[COMMENT]'; -- 修改欄位註釋 UPDATE information_schema.COLUMNS SET column_comment = '[COMMENT]' WHERE TABLE_SCHEMA= '[database_name]' AND TABLE_NAME='[table_name]' AND COLUMN_NAME= '[column_name]'
分析 3: 為什麼用 TIMESTAMP 表示時間 ?
交流 :- TIMESTAMP 和 int 一樣都是 4 位元組. 用它表示時間戳更精簡更友好.
- 業務不再關心時間的建立和更新相關業務程式碼. 省心, 省程式碼
CREATE TABLE [table_name] ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主鍵', update_time BIGINT NOT NULL COMMENT '更新時間', create_time BIGINT NOT NULL COMMENT '建立時間', [delete_time BIGINT DEFAULT NULL COMMENT '刪除時間',] [template] ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '模板表';MySQL DateTime 和 Timestamp 時區問題 分析 4: 為什麼是 utf8mb4 而不是 utf8 ?
MySQL 的 utf8 不是標準的 utf8 unicode 字符集編碼.
正規 UTF-8 編碼是使用 1-6 位元組表示 unicode 字元.
但 MySQL utf8 只使用了 1-3 位元組表示一個字元,
那麼當他遇到 4 位元組編碼以上的 unicode 字元, 如表情符號會發生意外. 所以 MySQL 在 5.5 之後版本推出了 utf8mb4 編碼, 完全相容以前的 utf8 編碼.CREATE TABLE 小部分場景
在我們籌備大型軟體和服務設計時候, 需要有人對所使用特性與設計以及業務把關和負責. 這些小部分場景主要圍繞在 主鍵 ID 和 AUTO_INCREMENT 設計取捨. 概要的科普下其相關特性.AUTO_INCREMENT
AUTO_INCREMENT id 我們習慣叫他 自增 id, 這種說法不準確容易引發誤解. 稍微具體點叫法insert 頻率 id. 哪怕 insert 失敗了, 這個 id 頻率也會 +1 (or +auto_increment_increment).倒排索引
同樣這個通用名詞也很人費解. 如果叫 反向索引 很好理解.
例如我們普通的 id -> data 是(正向)索引, data 中關鍵 key -> id 是反向(Inverted index)索引
倒排索引為什麼叫倒排索引?更加具體點, 我們看看 MySQL InnoDB 引擎中怎麼定義和實現 AUTO_INCREMENT.
1. InnoDB 中 AUTO_INCREMENT 配置大致說明
- 1.1 innodb_auto_inc_lock_mode=0 (traditional lock mode)
- 獲取表鎖 (AUTO-INC 鎖, 特殊表鎖), 語句執行結束後釋放, 不需要等事務結束.
- 分配的值也是一個個分配,是連續的. (如果事務 rollback 了這個 auto_increment 值就會浪費掉, 從而造成間隙)
- 1.2 innodb_autoinc_lock_mode=1 (consecutive lock mode, MySQL 8.0 之前預設選項)
- 對於不確定插入數量的語句(例如 INSERT ... SELECT, REPLACE ... SELECT 和 LOAD DATA)
- innodb_autoinc_lock_mode=0 一樣獲取表鎖, 其他的確定數量的語句在執行前先批量獲取 id,
- 之後再走的是輕量級互斥鎖, 如果其他事務已經獲取表鎖, 這個時候也需要等待.
- 1.3 innodb_autoinc_lock_mode=2 (interleaved lock mode, MySQL 8.0+ 預設)
- 採用樂觀鎖, CAS 更新計數器獲取. 正常情況效能最好, 因為沒有表鎖和輕量級互斥鎖.
- 但在高併發引發的 高 CPU load 場景會適得其反, 加劇這種 CPU 浪費.
- AUTO_INCREMENT CAS 頻率高, 同一個語句操作內部 CAS INC 大概率也會讓 id 間隙變大.
2. InnoDB 中 AUTO_INCREMENT 實現大致思路
在 MySQL 8.0 之前 AUTO_INCREMENT 值儲存在記憶體中. 每次重啟通過 select max id 初始化值.-- 大致方式通過行級鎖(排他鎖) MAX(id) -> AUTO_INCREMENT init value SELECT MAX(ai_col) FROM [table_name] FOR UPDATE;
在 MySQL 8.0 之後, 持久化儲存在磁碟. 每次更新會寫入 redo log 中, 也會刷入 innodb 引擎系統表中記錄下來.
如果 MySQL 正常關閉重啟, 會從引擎系統表中獲取計數器的值. 如果 MySQL 故障重啟, 也會從引擎系統表中獲取計數器的值; 並且從最後一個檢查點開始掃描 redo log 中記錄的計數器值; 取二者最大值作為新值. 但是這個處理邏輯也不能保證最後拿到的值是正確的. 如果在系統檔案落盤前崩潰, 那麼就可能拿到一個之前使用過的值. 這也是資料備份和同步時候可能引發主鍵衝突根源.小部分場景
通過對 InnoDB 的 AUTO_INCREMENT 瞭解, 大致猜測到他的優缺點和使用領域以及現狀.交流 1: 如果考慮分散式場景呢, 高效能領域呢 ?
這時候推薦使用分散式唯一 ID 生成演算法器. (用更復雜大炮幹複雜長槍) 替代 AUTO_INCREMENT. 補充說明, 在普通領域 AUTO_INCREMENT 也是個長槍級別 ID 生成器. 原理-- sequece id 生成器表 CREATE TABLE sequece ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主鍵, 自增 id', stub char(1) NOT NULL DEFAULT '' COMMENT '打樁靶子', UNIQUE KEY unique_key_stub (stub) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '古老分散式 id 生成器'; -- 獲取 id DELIMITER $ BEGIN REPLACE INTO sequece(stub) VALUES ('X'); SELECT LAST_INSERT_ID(); COMMIT $部署 我們也可以多臺機器部署, 設定不同 AUTO_INCREMENT step, 讓每個 sequece 產生不同號碼. 例如部署 step = 2 個服務結點, 並行獲取資料. 一個 from 1, 3, 5, 7, 9 ... 一個 from 2, 4, 6, 8, .. . 詳細部署操作指導
-- step 標識增長步長, 也標識分散式機器數 show global variables like 'auto_increment%' +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | auto_increment_increment | 1 | | auto_increment_offset | 1 | +--------------------------+-------+ -- auto_increment_increment 全域性步長 -- auto_increment_offset 自增起始值 -- 設定自增步長 -- set session 設定當前會話連結, set global 設定當前 ID 機器 set global auto_increment_increment=step for i : [0, step) CREATE TABLE sequece ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主鍵, 自增 id', stub char(1) NOT NULL DEFAULT '' COMMENT '打樁靶子', UNIQUE KEY unique_key_stub (stub) ) ENGINE=InnoDB AUTO_INCREMENT = [offset + i] DEFAULT CHARSET=utf8mb4 COMMENT = '古老分散式 id 生成器';這種古老 MySQL 分散式 ID 生成器, 方案成熟部署簡單. 在高併發領域存在 DB 效能瓶頸. 如果考慮高可用主從架構, 在主服務掛了, 從服務頂上時候存在重複發號可能. 關於 分散式唯一 ID 其它業界解決方案, 後面有機會再聊.
交流 2: 為什麼官方推薦 AUTO_INCREMENT 當主鍵, 而很少見到 UUID 等等?
MySQL InnoDB 引擎預設主鍵索引是 B+ 線索樹索引, 也稱為聚簇索引(聚集索引, row key 和 row value 存在連續記憶體中), 為何叫聚簇索引呢? 在 InnoDB 中, 每個表都會有一個聚簇索引, 在定義了主鍵(primary key)的情況下, 主鍵所在的列會被作為聚簇索引儲存. 所謂聚簇索引,意思是資料實際上是儲存在索引的葉子節點上, 「聚簇」的含義就是和相鄰的資料緊湊地儲存在一起. 因為不值得同時把資料行儲存在兩個不同的位置,所以一個表只能有一個聚簇索引. 關於 InnoDB 選擇哪個列作為聚簇索引儲存,大概的優先順序為: 如果定義了主鍵(primary key), 則使用主鍵; 如果沒有定義主鍵, 則選擇第一個不包含 NULL(NOT NULL)的 UNIQUE KEY; 如果也沒有, 則會隱式定義一個主鍵作為聚簇索引. 聚簇索引說明MySQL 讀取磁碟上的資料是一頁一頁讀取的, 如果某條我們要處理的資料在某一頁中, 但是這一頁其他資料我們都不關心, 這樣的請求多了, 效能會急劇下降, 類似於 CPU 的併發殺手 false sharing.
偽共享 (false sharing) 的非標準定義為:
快取系統中是以快取行 (cache line) 為單位儲存的. 當多執行緒修改互相獨立的變數時,
如果這些變數共享同一個快取行, 就會無意中影響彼此的效能, 這就是偽共享.按照 B+ 線索平衡樹的原理, AUTO_INCREMENT 的 ID 能保證最新的資料在一頁中被讀取, 而且減少了 B+ 樹分裂翻轉. UUID 等唯一 ID 由於無序, 插入時, B+ 樹會不斷翻轉, 並且最新的資料可能不在同一頁. 很可能會出現, 最新一條資料, 和好幾年前的資料在同一頁. 例如購物和支付交易的訂單, 節日促銷的抽獎活動這類業務都有這樣的使用場景, 訪問頻率在最近一天, 一週, 或者幾個月內比較活躍, 而超過一段時間內的資料很少訪問. 當然架構設計是當下業務和未來業務場景之間取捨. 拋開 MySQL AUTO_INCREMENT 的 ID 分散式和鎖效能瑕疵, 在嘗試分庫分表時候他就變得有點累贅.