我该如何处理耗时的SQL?

时间:2010-08-25 10:29:19

标签: database oracle query-optimization

我们有一个包含600万条记录的表,然后我们有一个SQL需要大约7分钟来查询结果。我认为SQL不能再优化了。

查询时间导致我们的weblogic抛出最大卡住线程异常。

我有什么建议可以解决这个问题吗?

以下是查询,但我很难改变它,

SELECT * FROM  table1 
WHERE trim(StudentID) IN ('354354','0') 
AND concat(concat(substr(table1.LogDate,7,10),'/'),substr(table1.LogDate,1,5)) 
       BETWEEN '2009/02/02' AND '2009/03/02' 
AND TerminalType='1' 
AND RecStatus='0' ORDER BY StudentID, LogDate DESC, LogTime

但是,我知道使用字符串比较日期是耗时的,但有人在我无法更改表格结构之前写了...

LogDate被定义为一个字符串,格式为mm / dd / yyyy,所以我们需要对它进行子串和连接,而不是在...之间使用...我认为这里很难优化。

10 个答案:

答案 0 :(得分:7)

这个查询很可能正在进行全文扫描,因为你在WHERE条件下不太可能利用任何索引。

LogDate是日期字段还是文本字段?如果它是一个日期字段,那么不要做substr和concat。只需说“2009-02-02”和“2009-02-03”之间的LogDate或者日期范围是什么。如果将其定义为文本字段,您应该认真考虑将其重新定义为日期字段。(如果您的日期确实如此是文本,写成mm / dd / yyyy然后你的ORDER BY ...如果日期超过一年,LOGDATE DESC将不会给出有用的结果。)

是否有必要对StudentID进行修剪?在将数据放入数据库之前清理数据然后在每次检索时尝试清理数据要好得多。

如果将LogDate定义为日期,并且您可以修改输入上的studentid,则在一个或两个字段上创建索引,查询时间应该会急剧下降。

或者,如果您想要快速而肮脏的解决方案,请在“trim(studentid)”上创建索引。

如果这没有帮助,请向我们提供有关表格布局和索引的更多信息。

答案 1 :(得分:4)

SELECT * ... WHERE trim(StudentID) IN ('354354','0')

如果这是普通构造,那么您需要function based index。因为没有它,您强制DB服务器执行全表扫描。

根据经验,您应该尽可能避免使用WHERE子句中的函数。 trim(StundentID)substr(table1.LogDate,7,10)阻止数据库服务器使用任何索引或对查询应用任何优化。尝试尽可能多地使用本机数据类型,例如对于DATEVARCHAR代替LogDate。还应在客户端软件中通过例如StudentID正确管理INSERTUPDATE / {{1}}之前调整数据。

答案 2 :(得分:3)

如果您的数据库支持它,您可能想尝试materialized view

如果没有,可能值得考虑自己实现类似的东西,通过一个运行查询的预定作业执行昂贵的修剪和连接,并刷新一个包含结果的表,以便您可以针对更好的方式运行查询桌子,避免昂贵的东西。或者使用触发器来维护这样一个表。

答案 3 :(得分:2)

如果没有关于您正在执行什么类型的查询以及是否使用索引的任何进一步信息,很难提供任何具体信息。

但这里有一些一般提示。

  1. 确保在经常过滤/排序的列上使用索引。
  2. 如果只是某个查询太慢,那么您可以通过在数据库更改时自动生成结果来阻止自己执行该查询。例如,代替count(),您通常可以将计数存储在某处。
  3. 尝试从查询中删除trim(),方法是在将数据插入表格之前/之后自动调用数据trim()。这样,您只需使用索引即可找到StudentID

    此外,date过滤器应该可以在数据库中本地使用。不知道哪个数据库可能更难,但这样的事情应该可行:LogDate BETWEEN '2009-02-02' AND '2009-02-02'

    如果您还在所有这些列上添加索引(例如StudentIDLogDateTerminalTypeRecStatusEmployeeID,那么它应该是闪电般快速。

答案 4 :(得分:2)

  

但查询时间会导致我们的weblogic抛出最大卡住线程异常。

如果查询需要7分钟且无法更快,则必须实时停止运行此查询。您是否可以更改应用程序以查询定期刷新的缓存结果表?

作为此前的紧急停顿,您可以实现一个锁存器(在Java中),一次只允许一个线程执行此查询。第二个线程会立即失败并出现错误(而不是将整个系统关闭)。这可能不会让这个查询的用户满意,但至少它可以保护其他人。

  

我更新了查询,你能给我一些建议吗?

这些字符串操作使得索引几乎不可能。你确定你至少不能摆脱“修剪”吗?实际数据中是否真的存在冗余空白?如果是这样,你可以缩小一个student_id,这应该可以加快速度。

您希望在(student_id,log_date)上使用复合索引,并且希望仍然可以使用索引范围扫描(对于给定的学生ID)来解析复杂的log_date条件。

答案 5 :(得分:1)

在不知道您正在使用什么数据库以及您的表结构是什么的情况下,很难建议任何改进,但可以通过使用索引,提示等来改进查询。

在您的查询中,以下部分 concat(concat(substr(table1.LogDate,7,10),'/'), substr(table1.LogDate,1,5)) BETWEEN '2009/02/02' AND '2009/02/02'

太搞笑了。 BETWEEN'2009 / 02/02'和'2009/02/02' ??男人,你想做什么?

你可以在这里发布你的表结构吗?

无论如何,600万条记录并不是一件大事。

答案 6 :(得分:1)

很多人都知道你的问题出在日期字段中。您肯定需要将日期从字符串字段更改为本机日期类型。如果它是您的应用中以这种方式使用的旧版字段 - 您仍然可以创建一个基于函数的to_date(logdate, 'DD/MM/YYYY')索引,将您的“字符串”日期转换为“日期”日期,并允许快速提到between搜索而不修改表格数据。

这应该可以加快速度。

答案 7 :(得分:0)

根据您提供的少量信息,我的预感是以下条款为我们提供了线索:

     ... WHERE trim(StudentID) IN ('354354','0') 

如果您有大量记录与未识别的学生(即studentID = 0),则studentID上的索引将非常不平衡。

在600万条记录中,有多少人有studentId = 0?

答案 8 :(得分:0)

您的主要问题是您的查询将所有内容视为字符串。

如果LogDate是没有时间组件的日期,则需要类似以下内容的

SELECT * FROM  table1 
WHERE  StudentID  IN (:SearchStudentId,0) 
AND  table1.LogDate = :SearchDate
AND TerminalType='1' 
AND RecStatus='0' 
ORDER BY EmployeeID, LogDate DESC, LogTime

如果LogDate有时间组件,而SearchDate没有时间组件,那么就是这样的。 (.99999将时间设置为午夜前的1秒)

SELECT * FROM  table1 
WHERE  StudentID  IN (:SearchStudentId,:StudentId0) 
AND  table1.LogDate BETWEEN :SearchDate AND :SearchDate+0.99999 
AND TerminalType='1' 
AND RecStatus='0' 
ORDER BY EmployeeID, LogDate DESC, LogTime

请注意,对于在调用之间更改的参数,请使用绑定变量。它不会使查询更快,但它是'最佳实践'。

根据您的调用语言,您可能需要添加TO_DATE等,以将传入的绑定变量强制转换为Date类型。

答案 9 :(得分:0)

如果StudentID是char(通常是使用trim()的原因),您可以通过填充变量而不是修剪字段来获得更好的性能,如下所示(假设StudentID是{ {1}}):

char(10)

这将允许使用StudentID上的索引(如果存在)。