假设有一个名为“log”的表,其中有大量记录。
应用程序通常通过简单的 SQL 检索数据:
SELECT *
FROM log
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)
logLevel
并且creationData
有索引,但是记录的数量使得检索数据需要更长的时间。
我们如何解决这个问题?
假设有一个名为“log”的表,其中有大量记录。
应用程序通常通过简单的 SQL 检索数据:
SELECT *
FROM log
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)
logLevel
并且creationData
有索引,但是记录的数量使得检索数据需要更长的时间。
我们如何解决这个问题?
查看您的执行计划/“EXPLAIN PLAN”结果 - 如果您正在检索大量数据,那么您几乎无法提高性能 - 您可以尝试将SELECT
语句更改为仅包含您感兴趣的列,但是它不会改变你正在做的逻辑读取的数量,所以我怀疑它只会对性能产生微不足道的影响。
如果您只检索少量记录,那么 LogLevel 索引和 CreationDate 索引应该可以解决问题。
更新: SQL 服务器主要用于查询大型数据库的小子集(例如,从数百万的数据库中返回单个客户记录)。它并没有真正准备好返回真正的大型数据集。如果您返回的数据量确实很大,那么您只能做一定的量,所以我不得不问:
你真正想要达到的目标是什么?
如果您向用户显示日志消息,那么他们一次只会对一小部分感兴趣,因此您可能还想研究分页 SQL 数据的有效方法 - 如果您只返回甚至说 500一次左右记录它应该仍然非常快。
如果您尝试进行某种统计分析,那么您可能希望将数据复制到更适合统计分析的数据存储中。(不确定是什么,这不是我的专业领域)
1:从不使用Select *
2:确保您的索引正确,并且您的统计信息是最新的
3:(可选)如果您发现您没有查看过去某个时间的日志数据(根据我的经验,如果它发生了一个多星期前,我可能不需要它的日志)设置一个作业将其存档到一些备份,然后删除未使用的记录。这将减小表大小,从而减少搜索表所需的时间。
根据您使用的 SQL 数据库类型,您可能会查看Horizaontal Partitioning。通常,这可以完全在数据库方面完成,因此您无需更改代码。
你需要所有的列吗?第一步应该是只选择那些你真正需要检索的。
另一个方面是在数据到达您的应用程序后您如何处理数据(填充数据集/按顺序读取它/?)。
在处理应用程序方面可能有一些改进的潜力。
你应该回答自己这些问题:
您是否需要一次将所有返回的数据保存在内存中?您在检索端为每行分配多少内存?您一次需要多少内存?你能重用一些内存吗?
几件事
你需要所有的列吗?人们通常会这样做,SELECT *
因为他们懒得列出表格中的 15 列中的 5 列。
获得更多 RAM,您拥有的 RAM 越多,缓存中的数据就越多,这比从磁盘读取快 1000 倍
对我来说,你可以做两件事,
根据日期列对表格进行水平分区
使用预聚合的概念。
预聚合: 在 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 秒。我打算尽快公开该插件。
根据其他答案,除非您确实需要所有字段,否则请勿使用“选择 *”。
logLevel 和 creationData 有索引
您需要具有两个值的单个索引,将它们放入的顺序会影响性能,但假设您有少量可能的日志级别值(并且数据没有倾斜),您将获得更好的性能,将 creationData 放在首位。
请注意,最佳索引将降低查询 log(N) 的成本,即随着记录数量的增加,它仍然会变慢。
C。
我真的希望creationData
你的意思creationDate
。
首先,在和上有索引是不够的。如果您有 2 个单独的索引,Oracle 将只能使用 1 个。您需要的是两个字段上的单个索引:logLevel
creationData
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 列上的范围对其进行分区。有关详细信息,请参阅以下链接: