美文网首页Flask博客
反范式设计博客表结构 - RebuildBlog - Day2

反范式设计博客表结构 - RebuildBlog - Day2

作者: FesonX | 来源:发表于2020-05-30 23:26 被阅读0次

    今日代码,可以 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
    

    公众号:程序员的碎碎念

    相关文章

      网友评论

        本文标题:反范式设计博客表结构 - RebuildBlog - Day2

        本文链接:https://www.haomeiwen.com/subject/crchzhtx.html