12

考虑下表:

CREATE TABLE user_roles(
    pkey         SERIAL PRIMARY KEY,
    bit_id       BIGINT NOT NULL,
    name         VARCHAR(256) NOT NULL,
);

INSERT INTO user_roles (bit_id,name) VALUES (1,'public');
INSERT INTO user_roles (bit_id,name) VALUES (2,'restricted');
INSERT INTO user_roles (bit_id,name) VALUES (4,'confidential');
INSERT INTO user_roles (bit_id,name) VALUES (8,'secret');

CREATE TABLE news(
    pkey          SERIAL PRIMARY KEY,
    title         VARCHAR(256),
    company_fk    INTEGER REFERENCES compaines(pkey), -- updated since asking the question
    body          VARCHAR(512),
    read_roles    BIGINT -- bit flag 
);

read_roles 是一个位标志,它指定可以阅读新闻项目的某些角色组合。因此,如果我要插入一个可以被受限和机密阅读的新闻项目,我会将 read_roles 的值设置为2 | 4或 6,并且当我想取回特定用户可以看到的新闻帖子时,我可以使用类似的查询。

select * from news WHERE company_fk=2 AND (read_roles | 2 != 0) OR  (read_roles | 4 != 0) ; 
select * from news WHERE company_fk=2 AND read_roles = 6; 

通常在数据库列中使用位标志有什么缺点?我假设这个问题的答案可能是特定于数据库的,所以我有兴趣了解特定数据库的缺点。

我正在为我的应用程序使用 Postgres 9.1。

更新我了解到数据库不使用索引进行位操作,这将需要全表扫描,这会降低性能。所以我更新了这个问题以更准确地反映我的情况,数据库中的每一行都属于一个特定的公司,所以所有的查询都将有 WHERE 子句,其中包含一个 company_fk ,它上面会有一个索引。

更新我现在只有 6 个角色,将来可能会更多。

UPDATE角色不是互斥的,它们相互继承,例如,restricted 继承了分配给 public 的所有权限。

4

4 回答 4

9

缺点:难以写入数据,难以读取数据,难以调试,尤其是:查询速度慢,因为数据库无法在这样的查询上使用索引。

优点是节省了几个字节。与 BIT 字段相比,您可以在一百万条记录表上节省几 MB ......几乎不值得。:)

于 2012-09-04T19:58:48.493 回答
9

如果你只有少数几个角色,你甚至不会在PostgreSQL中节省任何存储空间。一个列使用 4 个字节,一个8 个字节。两者都可能需要对齐填充:integerbigint

boolean列使用 1 个字节。integer实际上,您可以为一列安装四个或更多布尔列,为bigint.

还要考虑到值仅使用NULL 位图NULL中的一位(简化)。

单个列更易于阅读和索引。其他人已经对此发表了评论。

您仍然可以使用表达式索引部分索引来规避索引问题(“non-sargable”)。广义的陈述,如:

数据库不能在这样的查询中使用索引

或者

这些条件是非 SARGable 的!

并不完全正确- 也许对于其他一些缺乏这些功能的 RDBMS。
但是,当您可以完全避免问题时,为什么要规避呢?

正如您所澄清的,我们正在谈论 6 种不同的类型(也许更多)。使用单个boolean列。与一个相比,您甚至可能会节省空间bigint。在这种情况下,空间要求似乎无关紧要。


如果这些标志是互斥的,您可以使用列类型enum或一个小型查找表和一个引用它的外键。(排除有问题的更新。)

于 2012-09-04T20:02:19.770 回答
6

这里至少有一个巨大的缺点......

这些条件是非 SARGable 的!

这是一件大事,对我来说将是一个交易破坏者。您需要执行的按位评估(据我所知)在任何数据库中都不可索引 - 引擎需要检查每一行才能执行评估,这意味着性能很差。

于 2012-09-04T19:58:16.757 回答
2

添加到 SQL Server 实现的先前答案,您不会通过使用单个位域整数与一堆BIT NOT NULL列来节省任何空间:

SQL Server 数据库引擎优化了位列的存储。如果表中有 8 个或更少的位列,则这些列存储为 1 个字节。如果有 9 到 16位列,则这些列存储为 2 个字节,依此类推。

在 docs.microsoft.com 上位

正如 JNK 所提到的,对位域整数的部分比较不是 SARGable,因此位域整数上的索引将毫无用处,除非一次比较整个值。

SQL Server 上的磁盘索引是基于排序的,因此要访问单独设置了任何特定位的行,每个位列都需要一个单独的索引。如果您只查找 1,则一种节省空间的方法是让它们过滤列,只存储 1 值(零值根本没有索引条目)。

CREATE TABLE news(
    pkey          INT IDENTITY PRIMARY KEY,
    title         VARCHAR(256),
    company_fk    INTEGER REFERENCES compaines(pkey), -- updated since asking the question
    body          VARCHAR(512),
    public_role BIT NOT NULL DEFAULT 0,
    restricted_role BIT NOT NULL DEFAULT 0,
    confidential_role BIT NOT NULL DEFAULT 0,
    secret_role BIT NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX ByPublicRole ON news(public_role, pkey) WHERE public_role=1;
CREATE UNIQUE INDEX ByRestrictedRole ON news(restricted_role, pkey) WHERE restricted_role=1;
CREATE UNIQUE INDEX ByConfidentialRole ON news(confidential_role, pkey) WHERE confidential_role=1;
CREATE UNIQUE INDEX BySecretRole ON news(secret_role, pkey) WHERE secret_role=1;

select * from news WHERE company_fk=2 AND restricted_role=1 OR confidential_role=1; 
select * from news WHERE company_fk=2 AND restricted_role=1 AND confidential_role=1;

这两个查询都使用我生成的随机测试数据生成了一个不错的计划: 位计划

与往常一样,索引应基于实际查询使用情况并与维护成本相平衡。

于 2018-06-20T16:25:09.203 回答