【问题标题】:Appropriate query and indexes for a logging table in SQLSQL 中日志记录表的适当查询和索引
【发布时间】:2010-08-16 14:39:52
【问题描述】:

假设一个名为'log'的表,里面有大量的记录。

应用程序通常通过简单的 SQL 检索数据:

SELECT * 
FROM log 
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)

logLevelcreationData 有索引,但是记录的数量使得检索数据需要更长的时间。

我们如何解决这个问题?

【问题讨论】:

  • “解释计划”对您的查询有何影响?

标签: sql mysql sql-server oracle database-agnostic


【解决方案1】:

查看您的执行计划/“EXPLAIN PLAN”结果 - 如果您正在检索大量数据,那么您几乎无法提高性能 - 您可以尝试更改您的 SELECT 语句以仅包含您的列感兴趣,但是它不会改变您正在执行的逻辑读取的数量,因此我怀疑它对性能的影响可以忽略不计。

如果您只检索少量记录,那么 LogLevel 索引和 CreationDate 索引应该可以解决问题。

更新: SQL 服务器主要用于查询大型数据库的小子集(例如,从数百万的数据库中返回单个客户记录)。它并没有真正准备好返回真正的大型数据集。如果您返回的数据量真正很大,那么您只能做一定的量,所以我不得不问:

您真正想要实现的是什么?

  • 如果您向用户显示日志消息,那么他们一次只会对一小部分感兴趣,因此您可能还想研究分页 SQL 数据的有效方法 - 如果您一次只返回甚至说 500 条左右的记录,它仍然应该非常快。

  • 如果您尝试进行某种统计分析,那么您可能希望将数据复制到更适合统计分析的数据存储中。 (但不确定是什么,这不是我的专业领域)

【讨论】:

  • +1 用于询问问题背后的问题是“您实际上想要实现什么”
【解决方案2】:

1:永远不要使用Select *
2:确保您的索引是正确的,并且您的统计数据是最新的
3:(可选)如果您发现您没有查看过去某个时间的日志数据(根据我的经验,如果它发生在一个多星期前,我可能不需要它的日志)设置一个作业将其存档到某个备份,然后删除未使用的记录。这将减小表的大小,从而减少搜索表所需的时间。

【讨论】:

  • 我建议稍微改一下 - “确保你有正确的索引,并且你的 statistics 是最新的”,你的索引很少出现日期! :-)
  • @AllenG:愚蠢的问题:索引不是最新的会发生吗?我一直认为它是“自动的”。
  • @Kragen:公平。即将更新。
  • @Patrick - 索引可能永远过时,但是统计信息(SQL 服务器用来选择执行查询的最有效方式的数据)可能会过时日期。
  • @Kragen:小问题:可能有过期的索引。仅在您要索引的列已更改且您尚未更改要索引的列的情况下。不过,到那时,它肯定是基于碳的错误。
【解决方案3】:

根据您使用的 SQL 数据库类型,您可以查看Horizaontal Partitioning。通常,这可以完全在数据库方面完成,因此您无需更改代码。

【讨论】:

    【解决方案4】:

    您需要所有列吗?第一步应该是只选择那些你真正需要检索的。

    另一方面是在数据到达您的应用程序后您如何处理数据(填充数据集/按顺序读取它/?)。

    在处理应用程序方面可能有一些改进的潜力。

    你应该回答自己这些问题:

    您需要一次将所有返回的数据保存在内存中吗?您在检索端为每行分配多少内存?您一次需要多少内存?你能重用一些内存吗?

    【讨论】:

      【解决方案5】:

      几件事

      你需要所有的列吗?人们通常会使用SELECT *,因为他们懒得列出表格中的 15 列中的 5 列。

      获取更多内存,内存越大,缓存中的数据就越多,比从磁盘读取快 1000 倍

      【讨论】:

      • 更多内存?你的意思是我应该为缓存写算法?
      • 机器没有更多的 RAM,以便后续请求访问 RAM 而不是从磁盘获取数据
      • 更多 RAM 通常不是开发人员可以影响的选择,更不用说做出了。
      【解决方案6】:

      对我来说,你可以做两件事,

      1. 根据日期列对表格进行水平分区

      2. 使用预聚合的概念。

      预聚合: 在 preagg 中,您将有一个“logs”表、“logs_temp”表、一个“logs_summary”表和一个“logs_archive”表。 logs 和 logs_temp 表的结构是相同的。应用程序的流程是这样的,所有日志都记录在日志表中,然后每小时运行一个 cron 作业,它执行以下操作:

      一个。将日志表中的数据复制到“logs_temp”表并清空日志表。这可以使用影子表技巧来完成。

      b.从 logs_temp 表中聚合该特定小时的日志

      c。将汇总结果保存在汇总表中

      d。将记录从 logs_temp 表复制到 logs_archive 表,然后清空 logs_temp 表。

      通过这种方式,结果会预先汇总在汇总表中。

      当您希望选择结果时,您可以从汇总表中选择它。

      这种方式的选择非常快,因为记录的数量要少得多,因为数据是每小时预先聚合的。您甚至可以将阈值从一个小时增加到一天。这完全取决于您的需求。

      现在插入速度也会很快,因为日志表中的数据量并不多,因为它只保存最后一小时的数据,因此与非常大的数据相比,插入时的索引重新生成时间会非常少-set 因此使插入速度更快。

      你可以阅读更多关于影子表技巧here

      我在一个基于 wordpress 的新闻网站中采用了预聚合方法。我必须为新闻网站开发一个插件,它可以显示最近流行的(最近 3 天流行的)新闻项目,每天有大约 100K 的点击量,这个预先聚合的东西真的帮助了我们很多。查询时间从超过 2 秒下降到不到 1 秒。我打算尽快公开该插件。

      【讨论】:

        【解决方案7】:

        根据其他答案,除非您确实需要所有字段,否则不要使用“选择 *”。

        logLevel 和 creationData 有索引

        您需要一个包含两个值的索引,将它们放在什么顺序会影响性能,但假设您有少量可能的 loglevel 值(并且数据没有倾斜),您将获得更好的性能,将 creationData 放在首位.

        请注意,最佳索引会降低查询 log(N) 的成本,即随着记录数量的增加,它仍然会变慢。

        C.

        【讨论】:

          【解决方案8】:

          我真的希望creationData 是指creationDate

          首先,在logLevelcreationData 上有索引 是不够的。如果您有 2 个单独的索引,Oracle 将只能使用 1 个。 您需要的是两个字段上的单个索引:

          CREATE INDEX i_log_1 ON log (creationData, logLevel);
          

          请注意,我将 creationData 放在首位。这样,如果您只将该字段放在 WHERE 子句中,它仍然可以使用索引。 (只过滤日期似乎比只过滤日志级别更有可能)。

          然后,确保表中已填充数据(与您将在生产中使用的数据一样多)并刷新表上的统计信息。

          如果表很大(至少几十万行),使用以下代码刷新统计信息:

          DECLARE
            l_ownname          VARCHAR2(255) := 'owner'; -- Owner (schema) of table to analyze
            l_tabname          VARCHAR2(255) := 'log'; -- Table to analyze
            l_estimate_percent NUMBER(3) := 5;  -- Percentage of rows to estimate (NULL means compute)
          BEGIN
            dbms_stats.gather_table_stats (
               ownname => l_ownname ,
                tabname => l_tabname,
                estimate_percent => l_estimate_percent,
                method_opt => 'FOR ALL INDEXED COLUMNS',
                cascade => TRUE
            );
          END;
          

          否则,如果表很小,则使用

          ANALYZE TABLE log COMPUTE STATISTICS FOR ALL INDEXED COLUMNS;
          

          此外,如果表变大,您应该考虑在 creationDate 列上按范围对其进行分区。有关详细信息,请参阅以下链接:

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-09-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-25
            相关资源
            最近更新 更多