【问题标题】:Efficiently finding unique values in a database table有效地在数据库表中查找唯一值
【发布时间】:2011-01-15 19:26:39
【问题描述】:

我有一个包含大量行的数据库表。此表表示系统记录的消息。每条消息都有一个消息类型,并将其存储在表中它自己的字段中。我正在编写一个用于查询此消息日志的网站。如果我想按消息类型搜索,那么理想情况下,我希望有一个下拉框,列出数据库中出现的消息类型。消息类型可能会随着时间而改变,因此我无法将类型硬编码到下拉列表中。我将不得不进行某种查找。遍历整个表内容以查找唯一的消息值显然是非常愚蠢的,但是在数据库字段中是愚蠢的,我在这里寻求更好的方法。也许数据库偶尔会更新一个单独的查找表,其中仅列出我可以从中填充下拉列表的唯一消息类型会是一个更好的主意。

任何建议将不胜感激。

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

【问题讨论】:

  • 你在说多少行?百万? 10s/100s 百万?更多?

标签: asp.net sql sql-server asp.net-mvc database


【解决方案1】:

在消息类型上创建索引:

CREATE INDEX IX_Messages_MessageType ON Messages (MessageType)

然后要获得唯一的消息类型列表,您运行:

SELECT DISTINCT MessageType
FROM Messages
ORDER BY MessageType

因为索引是按MessageType的顺序物理排序的,SQL Server 可以非常快速、高效扫描索引,获取唯一消息类型的列表。

性能还不错 - 这是 SQL Server 擅长的。


诚然,您可以通过“message types”表节省一些空间。如果您一次只显示几条消息:那么书签查找,因为它连接回MessageTypes 表,不会有问题。但是,如果您开始一次显示成百上千条消息,那么返回到 MessageTypes 的连接可能会变得非常昂贵且不必要,并且将 MessageType 与消息一起存储会更快.

但我在MessageType 列上创建索引并选择distinct 没有问题。 SQL Server 喜欢这种东西。但是,如果你发现它对你的服务器来说是一个真正的负载,一旦你每秒获得几十个点击,然后按照另一个建议并将它们缓存在内存中。

我个人的解决方案是:

  • 创建索引
  • 选择不同的

如果我还有问题

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

关于规范化/非规范化问题。规范化可以节省空间,但会在不断执行连接时以 CPU 为代价。但是去口头化的逻辑点是避免重复数据,这会导致数据不一致。

您是否计划更改消息类型的文本,如果您将其与消息一起存储,则必须更新所有行?

或者对于消息类型“请求客户响应”这一事实有什么要说的?

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      正如其他人所说,创建一个单独的消息类型表。向消息表中添加记录时,请检查表中是否已存在消息类型。如果没有,请添加它。在任何一种情况下,然后将消息类型表中的标识符发布到消息表中。这应该为您提供标准化数据。是的,添加记录会花费一些额外的时间,但检索效率应该更高。

      如果有更多的添加然后读取,并且如果“消息类型”很短,则完全不同的方法是仍然创建单独的消息类型表,但在添加时不要引用它,只更新懒惰地,按需提供。

      即,(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;
      

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

      【讨论】:

        【解决方案4】:

        我只想说明一个显而易见的事实:规范化数据。

        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 不知道谁以及为什么投了反对票。没有解释的 IMO 否决票不是很好。
        【解决方案5】:

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

        类似

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

        根据这种设计,您的查找应该只查询 MessageTypes

        【讨论】:

          【解决方案6】:

          您是否考虑过索引视图?它的结果集被具体化并保存在存储中,因此查找的开销与您尝试执行的其他任何操作分开。

          当数据发生变化时,SQL Server 会自动更新视图,它认为这会改变视图的内容,因此在这方面它不如 Oracle 实现的灵活。

          【讨论】:

          • 您不能为带有DISTINCT 子句的查询的视图建立索引。
          【解决方案7】:
          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 会缓存结果并且它们不应该经常更改。
          • @RandomBen: SQL Server 缓存数据页,而不是结果。无论如何都需要进行全表或索引扫描,即使所有表(或索引)页都已缓存。对于足够大的表来说,这仍然需要很长时间。
          【解决方案8】:

          是的,我肯定会使用单独的查找表。然后,您可以使用以下内容填充它:

          INSERT TypeLookup (Type)
          SELECT DISTINCT Type
          FROM BigMassiveTable
          

          然后,您可以定期运行充值作业,从主表中提取查找表中尚不存在的新类型。

          【讨论】:

          • +1 用于处理查找表的持续维护。如果大表和查找表都具有“插入时间戳”,则可以通过仅检查新消息类型的“新”记录来提高周期性作业的效率。或者,BigMassiveTable 上的 INSERT 触发器可以在没有常规批处理作业的情况下完成这项工作。
          • @pilcrow - 是的,我认为充值方法是最好的,而不是触发器 - 触发器会对每个插入造成打击,所以我将其保持为“非高峰”尤其是在添加新类型不是很频繁的情况下。
          【解决方案9】:

          带有存储在日志中的消息类型 ID 的单独查找表。这将减小日志的大小并提高日志的效率。它还会Normalize您的数据。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-01-13
            • 1970-01-01
            • 2010-12-13
            • 1970-01-01
            • 1970-01-01
            • 2016-10-26
            • 1970-01-01
            相关资源
            最近更新 更多