1

假设有一个名为“log”的表,其中有大量记录。

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

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

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

我们如何解决这个问题?

4

8 回答 8

5

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

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

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

你真正想要达到的目标是什么?

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

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

于 2010-08-16T14:43:52.230 回答
4

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

于 2010-08-16T14:43:00.110 回答
2

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

于 2010-08-16T14:50:25.760 回答
1

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

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

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

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

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

于 2010-08-16T14:44:24.183 回答
0

几件事

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

获得更多 RAM,您拥有的 RAM 越多,缓存中的数据就越多,这比从磁盘读取快 1000 倍

于 2010-08-16T14:42:43.090 回答
0

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

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

  2. 使用预聚合的概念。

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

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

湾。从 logs_temp 表中聚合该特定小时的日志

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

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

这样,结果就会预先汇总在汇总表中。

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

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

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

您可以在此处阅读有关影子表技巧的更多信息

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

于 2010-08-16T15:56:40.657 回答
0

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

logLevel 和 creationData 有索引

您需要具有两个值的单个索引,将它们放入的顺序会影响性能,但假设您有少量可能的日志级别值(并且数据没有倾斜),您将获得更好的性能,将 creationData 放在首位。

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

C。

于 2010-08-16T16:01:02.963 回答
0

我真的希望creationData你的意思creationDate

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

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 列上的范围对其进行分区。有关详细信息,请参阅以下链接:

于 2010-08-17T05:47:55.590 回答