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

Posted FesonX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反范式设计博客表结构 - 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

公众号:程序员的碎碎念

以上是关于反范式设计博客表结构 - RebuildBlog - Day2的主要内容,如果未能解决你的问题,请参考以下文章

数据库范式设计 和 反范式化设计

MySQL调优表结构优化

MySQL数据库设计常犯的错以及对性能的影响

数据库中的范式和反范式详解!

数据库优化之结构设计

Java学习笔记:数据库中的范式和反范式