假设一个名为'log'的表,其中有大量记录。
应用程序通常通过简单的SQL检索数据:
SELECT *
FROM log
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)
logLevel
和creationData
具有索引,但记录数量使得检索数据需要更长时间。
我们如何解决这个问题?
答案 0 :(得分:5)
查看执行计划/“EXPLAIN PLAN”结果 - 如果您要检索大量数据,那么您可以做很少的事情来提高性能 - 您可以尝试将SELECT
语句更改为仅包括您感兴趣的列,但它不会改变您正在执行的逻辑读取次数,因此我怀疑它只会对性能产生可忽略的影响。
如果您只检索少量记录,那么LogLevel的索引和CreationDate上的索引应该可以解决问题。
更新: SQL服务器主要用于查询大型数据库的小型子集(例如,从数百万的数据库中返回单个客户记录)。它并没有真正适应返回真正大型的数据集。如果您要返回的数据量真正大,那么您将只能执行一定数量的操作,因此我不得不问:
你实际上试图实现是什么?
如果您正在向用户显示日志消息,那么他们一次只会对一个小的子集感兴趣,因此您可能还需要研究分配SQL数据的有效方法 - 如果您只回来说,一次说500左右的记录,它应该仍然非常快。
如果您尝试进行某种统计分析,那么您可能希望将数据复制到更适合统计分析的数据存储中。 (不知道那是什么,这不是我的专业领域)
答案 1 :(得分:4)
1:永远不要使用Select *
2:确保您的索引正确无误,并且您的统计信息是最新的
3 :(可选)如果你发现你没有在一定时间内查看日志数据(根据我的经验,如果它发生在一周以前,我可能根本不需要它的日志)设置一个将作业存档到某些备份,然后删除未使用的记录。这将使表格大小减少,从而减少搜索表格所需的时间。
答案 2 :(得分:2)
根据您正在使用的SQL数据库类型,您可能会查看Horizaontal Partitioning。通常,这可以完全在数据库方面完成,因此您不需要更改代码。
答案 3 :(得分:1)
你需要所有专栏吗?第一步应该是只选择你真正需要检索的那些。
另一个方面是您在数据到达应用程序后对数据进行处理(填充数据集/按顺序读取/?)。
处理应用程序方面可能有一些改进的可能性。
你应该回答这些问题:
您是否需要立即将所有返回的数据保存在内存中?你在检索端每行分配多少内存?你一次需要多少内存?你能重用一些记忆吗?
答案 4 :(得分:0)
一些事情
您是否需要所有列,人们通常会SELECT *
,因为他们懒得列出表格中的15列中的5列。
获得更多内存,更多内存,你拥有更多数据可以存储在缓存中,这比从磁盘读取快1000倍
答案 5 :(得分:0)
对我来说,你可以做两件事,
根据日期列
使用预聚合的概念。
<强>预聚合强> 在preagg中,您将拥有“logs”表,“logs_temp”表,“logs_summary”表和“logs_archive”表。 logs和logs_temp表的结构完全相同。应用程序流将以这种方式,所有日志都记录在日志表中,然后每小时运行一个cron作业,执行以下操作:
一个。将日志表中的数据复制到“logs_temp”表并清空日志表。这可以使用暗影表技巧来完成。
湾从logs_temp表
聚合该特定小时的日志℃。将汇总结果保存在汇总表
中d。将记录从logs_temp表复制到logs_archive表,然后清空logs_temp表。
这种结果在汇总表中预先汇总。
每当您想要选择结果时,您都可以从摘要表中选择它。
这种选择非常快,因为记录的数量远远少于每小时预先聚合的数据。您甚至可以将阈值从一小时增加到一天。这一切都取决于您的需求。
现在插入速度也很快,因为日志表中的数据量不多,因为它仅保留最后一小时的数据,因此与非常大的数据相比,插入时的索引重新生成所需的时间非常短-set因此使插入快速。
您可以阅读有关Shadow Table技巧here
的更多信息我在基于wordpress的新闻网站上使用了预聚合方法。我不得不为新闻网站开发一个插件,该插件会显示最近流行的(过去3天流行的)新闻项目,每天有100K次点击,这个预聚合的东西确实帮了我们很多。查询时间从2秒以上下降到不到2秒。我打算尽快公布插件。
答案 6 :(得分:0)
根据其他答案,除非你真的需要所有字段,否则不要使用'select *'。
logLevel和creationData具有索引
你需要一个包含这两个值的索引,你输入它们的顺序会影响性能,但假设你有少量可能的loglevel值(并且数据没有偏差)你将获得更好的性能将creationData放在首位
请注意,最佳索引会降低查询记录的成本(N),即随着记录数量的增加,它仍然会变慢。
下进行。
答案 7 :(得分:0)
我真的希望creationData
代表creationDate
。
首先,在logLevel
和creationData
上拥有索引是不够的。如果您有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列上按范围对其进行分区。有关详细信息,请参阅以下链接: