763 words
4 minutes
MySQL 中纵表的运用
起因
因为需求的迭代,用户表每过一段时间就需要添加一些字段。但是用户表数据量较大且在业务时间更新十分频繁。在业务频繁的时候,如果执行 Alter Table 这些 DDL 语句,可能会导致出现大量的 waitting for table metadata lock 锁住表,继而导致用户数据读写受到影响、服务不可用。 那么如何解决这种时不时要给用户添加新字段的情况呢。我们可以考虑使用纵表。
什么是纵表
横表是我们最经常使用的建表方式,既结构为主键、字段1、字段2、字段3。。。
。而纵表呢,其结构为主键、字段代码、字段值
。
举个例子,学生和考试科目的关系。
正常的横表建表方式应该是
学生名称 | 语文 | 数学 | 英语 | 生物 | … |
---|---|---|---|---|---|
A | 90 | 90 | 90 | 90 | … |
B | 90 | 90 | 90 | 90 | … |
… | … | … | … | … | … |
优点:一行表示了一个实体记录,清晰可见,一目了然。 缺点:如果现在要把这个表加一个字段,那么就必须重建表结构。
而使用纵表的建表方式,则为
学生名称 | 科目 | 分数 |
---|---|---|
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='用户特定信息详情表';
特别点:
- 我们定义主键时,使用
userId
和field
,这样子一来能加快通过userId
和field
查询value
的速度,二来保证userId
和field
的关系唯一。 - 我们加了一个
create_time
用于标识字段属性创建的时间,在需要查询某个用户设定该字段是什么时候时能起到作用 - 为什么还需要
field
和value
值的索引呢?一个很实际的场景:查询具有某个属性=某个值的所有用户 (select user_id where field='xxx' and value='xxx'
)。这种情况下,就需要idx_field_value
来加快查询速度 - 对于该表,还能进行一个优化,就是将
field
字段设定成int
类型,然后使用一个字典来保存 field 字段的可能值。 比如上面学生考试科目的场景,可以将科目存储为{1: "语文", 2:"数学", 3: "英语"}
, 每条记录就变成
学生名称 | 科目 | 分数 |
---|---|---|
A | 1 | 90 |
这样子,虽然可读性有所下降,但是存储所占用的空间就会减少,且理论上,查询匹配速度更快一点。