在数据库表中高效地查找唯一值

4
我有一个非常庞大的数据库表,用于记录系统产生的消息。每个消息都有一个消息类型,并存储在表中的一个字段中。我正在编写一个网站来查询这些消息日志。如果我想按消息类型搜索,则最好能够有一个下拉框列出在数据库中出现的消息类型。消息类型可能会随时间而变化,因此我不能将类型硬编码到下拉列表中。我需要进行某种查找。遍历整个表内容以查找唯一的消息值显然是非常愚蠢的,但作为一个数据库领域的外行者,在这里寻求更好的方法。也许可以创建一个单独的查找表,该表偶尔会进行更新,列出只包含唯一消息类型的表,我可以从中填充我的下拉列表。

非常感谢您提供任何建议。

我使用的平台是ASP.NET MVC和SQL Server 2005


你说的是多少行?百万级别的吗?还是千万/亿级别的?更多吗? - AdaTheDev
9个回答

9

在您的日志中存储消息类型id的单独查找表。这将减小日志的大小并提高效率。此外,它还可以规范化您的数据。


5

我会选择使用单独的查找表。您可以使用以下方式填充它:

INSERT TypeLookup (Type)
SELECT DISTINCT Type
FROM BigMassiveTable

您可以定期执行一项充值作业,从主表中拉取尚未存在于查找表中的新类型。

+1 是为了处理查找表的持续维护。如果大表和查找表都有“插入时间戳”,那么定期作业可以通过仅检查新消息类型的“新”记录来更有效地进行。或者,对于 BigMassiveTable 的 INSERT 触发器可以在没有常规批处理作业的情况下完成该工作。 - pilcrow
@pilcrow - 是的,我认为最好采用增量方法,而不是触发器 - 触发器会在每次插入时产生负担,因此如果添加新类型不是非常频繁,我会将其保持为“非高峰”任务。 - AdaTheDev

2
SELECT  DISTINCT message_type
FROM    message_log

这是最直接但不是非常高效的方法。

如果您有一个可能出现在日志中的类型列表,请使用以下方法:

SELECT  message_type
FROM    message_types mt
WHERE   message_type IN
        (
        SELECT  message_type
        FROM    message_log
        )

如果message_log.message_type被索引,这将更加高效。

如果您没有这个表但想创建一个,并且message_log.message_type被索引,请使用递归的CTE来模拟松散索引扫描:

WITH    rows (message_type) AS
        (
        SELECT  MIN(message_type) AS mm
        FROM    message_log
        UNION ALL
        SELECT  message_type
        FROM    (
                SELECT  mn.message_type, ROW_NUMBER() OVER (ORDER BY mn.message_type) AS rn
                FROM    rows r
                JOIN    message_type mn
                ON      mn.message_type > r.message_type
                WHERE   r.message_type IS NOT NULL
                ) q
        WHERE   rn = 1
        )
SELECT  message_type
FROM    rows r
OPTION (MAXRECURSION 0)

这不是相对高效的想法吗?因为SQL Server会缓存结果,而且它们不应该经常更改。 - Ben Hoffman
@RandomBen:SQL Server 缓存数据页面,而不是结果。即使所有表格(或索引)页面都被缓存,仍然需要进行完整的表格或索引扫描。对于足够大的表格,这仍然需要很长时间。 - Quassnoi

1

我只是想说一下显而易见的事情:规范化数据。

message_types
message_type | message_type_name

messages
message_id | message_type | message_type_name

那么你可以不使用任何缓存的 DISTINCT:

对于你的下拉菜单

SELECT * FROM message_types

为您的检索

SELECT * FROM messages WHERE message_type = ? 

SELECT m.*, mt.message_type_name FROM messages AS m
JOIN message_types AS mt
ON ( m.message_type = mt.message_type)

我不确定为什么你想要一个需要更新的缓存DISTINCT,当你可以稍微调整架构并拥有一个带有RI的。


有道理。+1 不确定是谁以及为什么会给踩。在我看来,没有解释的踩并不太好。 - A-K

1
在消息类型上创建一个索引:
CREATE INDEX IX_Messages_MessageType ON Messages (MessageType)

然后要获取唯一的消息类型(Message Types)列表,你可以运行以下代码:

SELECT DISTINCT MessageType
FROM Messages
ORDER BY MessageType

由于索引按照MessageType的物理顺序排序,SQL Server可以非常快速和高效地扫描索引,获取唯一消息类型的列表。

它的性能并不差 - 这是SQL Server擅长的领域。


可以通过创建一个"消息类型"表来节省一些空间。如果您一次只显示几条消息:那么由于它会与MessageTypes表连接,所以书签查找不会成为问题。但是,如果您一次显示数百或数千条消息,则连接回MessageTypes可能会变得非常昂贵和无意义,并且将MessageType存储在消息中将更快。

但是,我对在MessageType列上创建索引并选择distinct没有任何问题。 SQL Server喜欢这种东西。但是,如果您发现它对服务器造成了真正的负载,一旦您每秒钟获得数十个点击,那么请遵循其他建议并将它们缓存在内存中。

我的个人解决方案是:

  • 创建索引
  • 选择distinct

如果我仍然有问题

  • 在内存中缓存,30秒后过期

关于规范化/非规范化问题。规范化可以节省空间,但在频繁执行连接操作时会增加 CPU 负担。而非规范化的逻辑点在于避免重复数据,这可能导致数据不一致。
您是否计划更改消息类型的文本,如果将其存储在消息中,则必须更新所有行?
还是说,在消息产生时,消息类型确实是“请求客户端响应”?

0

消息类型应该是主表中指向包含消息类型代码和描述的定义表的外键。这将大大提高您的查找性能。

类似于:

DECLARE @MessageTypes TABLE(
        MessageTypeCode VARCHAR(10),
        MessageTypeDesciption VARCHAR(100)
)

DECLARE @Messages TABLE(
        MessageTypeCode VARCHAR(10),
        MessageValue VARCHAR(MAX),
        MessageLogDate DATETIME,
        AdditionalNotes VARCHAR(MAX)
)

从这个设计中,你的查询应该只查询MessageTypes

0

正如其他人所说,创建一个单独的消息类型表。当您向消息表中添加记录时,请检查消息类型是否已存在于该表中。如果不存在,则添加它。在任何情况下,然后将来自消息类型表的标识符发布到消息表中。这应该为您提供规范化的数据。是的,当您添加记录时需要额外的时间,但在检索时应该更有效率。

如果添加的数量比读取的数量多得多,并且“消息类型”很短,则完全不同的方法是仍然创建单独的消息类型表,但在进行添加时不引用它,并且仅在需要时惰性地更新它。

即,(a)在每个消息记录中包含时间戳。 (b)保留上次检查时找到的消息类型列表。 (c)每次检查时,搜索自上次以来添加的任何新消息类型,如下所示:

create table temp_new_types as
    (select distinct message_type
    from message
    where timestamp>last_type_check
);

insert into message_type_list (message_type)
select message_type
from temp_new_types
where message_type not in (select message_type from message_type_list);

drop table temp_new_types;

然后将此检查的时间戳存储在某个地方,以便下次使用。


0
你考虑过使用索引视图吗?它的结果集被实例化并持久化存储,因此查找的开销与你尝试执行的其他操作分离。
当数据发生更改时,SQL Server会自动更新视图,以便更新视图内容,因此在这方面,它比Oracle materialized不够灵活。

1
无法在带有DISTINCT子句的查询上索引视图。 - Quassnoi

0
答案是使用“DISTINCT”,而每个最佳解决方案对于不同大小的表格都是不同的。数千行,数百万行,数十亿行?这些都有非常不同的最佳解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接