763 words
4 minutes
MySQL 中纵表的运用
2019-09-10

起因#

因为需求的迭代,用户表每过一段时间就需要添加一些字段。但是用户表数据量较大且在业务时间更新十分频繁。在业务频繁的时候,如果执行 Alter Table 这些 DDL 语句,可能会导致出现大量的 waitting for table metadata lock 锁住表,继而导致用户数据读写受到影响、服务不可用。 那么如何解决这种时不时要给用户添加新字段的情况呢。我们可以考虑使用纵表。

什么是纵表#

横表是我们最经常使用的建表方式,既结构为主键、字段1、字段2、字段3。。。。而纵表呢,其结构为主键、字段代码、字段值。 举个例子,学生和考试科目的关系。 正常的横表建表方式应该是

学生名称语文数学英语生物
A90909090
B90909090

优点:一行表示了一个实体记录,清晰可见,一目了然。 缺点:如果现在要把这个表加一个字段,那么就必须重建表结构。

而使用纵表的建表方式,则为

学生名称科目分数
A语文90
A数学90
A英语90
A生物90
A

优点:如果现在要把这个表加一个字段,只需要添加一些记录。 缺点:数据描述不是很清晰,而且会造成数据库数据很多(这个时候,最好将数据表分表处理来缓解数据膨胀)。

应用#

回到最开始的问题,我们需要解决给用户增加属性字段的问题。那么我们可以想到,大概结构为”用户 ID,字段,属性值”。具体 SQL 语句如下:

CREATE TABLE `user_extra` (
`user_id` bigint(20) NOT NULL,
`field` varchar(50) NOT NULL,
`value` varchar(255) DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`,`field`),
KEY `idx_field_value` (`field`,`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户特定信息详情表';

特别点:

  1. 我们定义主键时,使用userIdfield,这样子一来能加快通过userIdfield查询value的速度,二来保证userIdfield的关系唯一。
  2. 我们加了一个create_time用于标识字段属性创建的时间,在需要查询某个用户设定该字段是什么时候时能起到作用
  3. 为什么还需要fieldvalue值的索引呢?一个很实际的场景:查询具有某个属性=某个值的所有用户 (select user_id where field='xxx' and value='xxx')。这种情况下,就需要idx_field_value来加快查询速度
  4. 对于该表,还能进行一个优化,就是将field字段设定成int类型,然后使用一个字典来保存 field 字段的可能值。 比如上面学生考试科目的场景,可以将科目存储为{1: "语文", 2:"数学", 3: "英语"}, 每条记录就变成
学生名称科目分数
A190

这样子,虽然可读性有所下降,但是存储所占用的空间就会减少,且理论上,查询匹配速度更快一点。

MySQL 中纵表的运用
https://hexzhou.github.io/posts/00003-mysql-vertical-table/
Author
Hex
Published at
2019-09-10
License
CC BY-NC-SA 4.0