反範式設計博客表結構 - RebuildBlog - Day2

今日代碼,可以 checkout ff5e9bb

在這次重寫博客時,設計數據庫部分將考慮反範式設計,最關鍵一點就是捨棄所有外鍵,通過代碼邏輯來保證表之間的聯繫。雖然我的博客可能沒有多大訪問量,但這個設計早已被驗證。

關於 MySQL 表結構的設計和使用上的一些建議可參見書籍《高性能 MySQL(High Performance MySQL)》以及文檔 啓發式規則建議-By Xiaomi,最基本的一點就是,設計列名之前,先搜下名稱是否在 MySQL 保留關鍵字裏(不使用文檔中標有 R 的詞,慎重使用其他關鍵詞)。

表設計構思

通用表設置

這裏設置兩個主要參數,一個是默認爲 innoDB 引擎,另一個是設置字符集爲utf8mb4,避免一些編碼問題。

這兩個設置放置在一個字典中,作爲模型的表參數(__table_args__

# Detail MySQL on SQLAlchemy, ref: https://docs.sqlalchemy.org/en/13/dialects/mysql.html
COMMON_TABLE_SETTINGS = {
    'mysql_charset': 'utf8mb4',
    'mysql_engine': 'InnoDB'
}

通用字段和抽象類

共有 5 張表,每張表都有 自增 id (unsigned bigint)創建時間 (create_time)更新時間 (update_time) 三個字段。其中更新時間將會自動隨着記錄更新而更新。

雖然 SQLAlchemy 的模型類可以對列設置 onupdate=func.now(),但根據其官方文檔以及 db.create_all() 生成的表結構來看,這只是通過客戶端生成的 SQL 來保證,表結構上的 update_time 並沒有 ON UPDATE CURRENT_TIMESTAMP,這即是文檔上所說的 Client-Invoked SQL Expressions,只是 DML,不會生成 DDLserver_default nullable
這些參數就會生成 DDL。

既然是通用字段,自然而然地想到用一個基類來定義這些字段,其他模型繼承即可。SQLAlchemy 設置抽象模型只需要簡單地加上 __abstract__,在博客中用的是最簡單的抽象方式,如果你想了解更高級的用法,可參見 Mixin and Custom Base Classes

三個通用字段的 DDL 表示:

-- 三個通用字段
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

對應的類設計:

class BaseModel(db.Model):
    __abstract__ = True
    __tablename__ = ''
    # Configuration Document: https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html
    __table_args__ = ''
    id = db.Column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
    create_time = db.Column(db.DateTime, server_default=func.now())
    # This is a client-side update trigger.
    # Will not be reflect on db.
    update_time = db.Column(db.DateTime, server_default=func.now(), onupdate=func.now())

    def __repr__(self):
        pass

變更爲默認自動更新的語句如下, 單獨放在 setup.py 中與創建表一起執行

ALTER TABLE table_name CHANGE `update_time` `update_time` datetime 
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'

儘量選擇無符號整型——包括 IP 地址

博客訪問中暫時沒有想到與計算相關且有意義的負值,因此整型都設置爲無符號,擴大數值範圍(指正數),而 IP 地址實際上可以通過移位操作來進行轉化,MySQL、Python等也有內置的函數或庫來進行 IP 地址和整數的轉化。因此,在設計時,也把原先的 VARCHAR 類型改爲 unsigned int,節省存儲空間。

爲所有字段設置默認值

除了文章表 body 字段爲 TEXT 不能設置默認值外,其他表字段均設置默認值。需要注意的是,TEXT 不能設置默認值,因此也不設置 NOT NULL 限制。

邏輯上定長,物理上變長的密碼字段

密碼字段爲了安全性考慮,存儲的是哈希值,用了 VARCHAR(128),通過類的設置密碼方法使保存的密碼都是定長的哈希值。

表設計落地

用戶認證表——用於登錄

CREATE TABLE `tbl_user_auth` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `password_hash` varchar(128) NOT NULL DEFAULT '',
  `email` varchar(128) NOT NULL DEFAULT '',
  `username` varchar(64) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  KEY `ix_user_auth_email` (`email`),
  KEY `ix_user_auth_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

用戶信息表——公開展示的信息

不使用外鍵,通過代碼邏輯來實現關聯,下同。

CREATE TABLE `tbl_user_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `username` varchar(64) NOT NULL DEFAULT '',
  `country` varchar(8) DEFAULT '',
  `city` varchar(32) DEFAULT '',
  `avatar` varchar(128) DEFAULT '',
  `intro` varchar(128) DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  KEY `ix_user_info_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

文章表

由於 column 是 MySQL 的關鍵字,這裏改用 section,由於專欄與文章是一對一,因此暫不對這個字段做冗餘,只進行索引。

CREATE TABLE `tbl_article` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `username` varchar(64) NOT NULL DEFAULT '',
  `title` varchar(128) NOT NULL DEFAULT '',
  `body` text,
  `slug` varchar(256) NOT NULL DEFAULT '',
  `section` varchar(64) DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `ix_article_title` (`title`),
  KEY `ix_article_username` (`username`),
  KEY `ix_article_section` (`section`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

評論表

暫時保留評論體作爲索引

CREATE TABLE `tbl_comment` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `username` varchar(64) NOT NULL DEFAULT '',
  `body` varchar(256) NOT NULL DEFAULT '',
  `ip` int(10) unsigned DEFAULT '0',
  `country_code` varchar(8) DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `ix_comment` (`body`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

標籤表

與文章是多對多的關係,單獨設置一張表來保存

CREATE TABLE `tbl_tag` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `tag` varchar(32) NOT NULL DEFAULT '',
  `username` varchar(64) DEFAULT '',
  `title` varchar(128) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `ix_tag_tag` (`tag`),
  KEY `ix_tag_title` (`title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

公衆號:程序員的碎碎念

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章