Logo
Section 1 / 3

概要:乱穿马路

让我们看看使用逗号分隔列表是如何导致反模式的。

导入

概要:乱穿马路

让我们看看使用逗号分隔列表是如何导致反模式的。

让我们设想你正在开发一个缺陷追踪应用程序中的一个功能,用于指定某个用户为某产品的主要联系人。最初,你只允许每个产品对应一个联系人。然而,不出所料,很快你就被要求支持为某个产品分配多个联系人。

当时,你认为只需简单地修改数据库,将原先存储单个用户账号标识符的地方改为用逗号分隔的用户ID列表即可。

不久后,你的老板来找你,提出了一个问题:“工程部门在他们的项目中添加了助理人员。他们告诉我最多只能添加五个人。如果尝试添加更多,就会报错。这是怎么回事?”

你点点头,说道:“嗯,项目上能列出的人数是有限的。” 仿佛这再正常不过。

但察觉到老板需要更精确的解释后,你迅速补充道:“呃……大概五到十个人,也许还能再多几个,具体取决于每个人账户的创建时间。” 然而,老板开始皱眉,你意识到这个问题无法轻描淡写地一带而过,于是继续解释:“我把项目的账户ID存成一个用逗号分隔的字符串。但这个字符串长度有上限。如果账户ID较短,就能容纳更多。早期创建的账户ID一般不超过99,所以比较短;而后来的ID超过了99,长度也变长了。”

老板皱起了眉头。你感觉今晚恐怕要加班了。

程序员常常使用逗号分隔的列表来避免为多对多关系创建中间关联表。这种反模式通常被称为 乱穿马路(Jaywalking)——这个名字源于“绕过交叉路口”的行为。

...
Jaywalking

目标:存储多值属性

当表中的一列仅包含单一值时,设计非常直接:我们可以选择一种 SQL 数据类型来表示该值的一个实例——例如 integer、date 或 string。但如何在一个列中存储一组相关的值呢?

在缺陷追踪数据库的例子中,我们可以通过 Products 表中的一个 integer 列将产品与联系人关联起来。每个账户可以拥有多个产品,而每个产品只关联一个联系人,因此产品与账户之间存在多对一的关系。

1CREATE TABLE Products ( 2 product_id SERIAL PRIMARY KEY, 3 product_name VARCHAR(1000), 4 account_id BIGINT UNSIGNED, 5 -- . . . 6 FOREIGN KEY (account_id) REFERENCES Accounts(account_id) 7); 8 9INSERT INTO Products (product_id, product_name, account_id) 10 VALUES (DEFAULT, 'Visual TurboBuilder', 12);

随着项目的发展,我们意识到一个产品可能有多个联系人。因此,除了原有的多对一关系外,我们还需要支持从产品到账户的一对多关系。这意味着 Products 表中的一行数据必须能够关联多个联系人。

该反模式的合理应用场景

通过“去规范化”(denormalization)的方式,我们可以提升某些查询的性能。将列表以逗号分隔的字符串形式存储,就是一种去规范化的做法。

例如,我们的应用可能需要以逗号分隔的格式获取数据,并且根本不需要单独访问列表中的各个元素。又或者,如果我们的应用从其他系统接收的是逗号分隔的数据格式,而我们只需要将整个列表原样保存并在之后完整取出,那么就没有必要拆分这些值。

总体原则是:若决定采用去规范化,应保持谨慎。通常最好先使用规范化的数据库结构,因为这样能让应用程序代码更具灵活性,同时也有助于数据库自身维护数据完整性。

简介

反模式:格式化逗号分隔的列表