查询优化大约需要一分钟才能执行

时间:2013-12-10 21:51:13

标签: sql sql-server query-optimization

有一个存储IIS日志的简单表。

表模式如下:

CREATE TABLE [dbo].[TBL_iisLog](
    [cdate] [varchar](50) NULL,
    [ctime] [varchar](50) NULL,
    [serverip] [varchar](50) NULL,
    [uri] [varchar](255) NULL,
    [port] [varchar](50) NULL,
    [username] [varchar](50) NULL,
    [clientip] [varchar](50) NULL,
    [useragent] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

请注意,我已在以下字段中添加了这三个索引:

  • username
  • clientip
  • cdatectime
  • 的组合

对于该表,以下查询执行大约需要50-60秒:

SELECT TOP 10
    username Usename,

    (SELECT COUNT(DISTINCT SUBSTRING(useragent, PATINDEX('%SIG:+%',useragent) + 5, 36))
        FROM tbl_iislog AS SoftwareSignatureCountTempTable
        WHERE (PATINDEX('%SIG:+%',useragent) > 0)
            AND SoftwareSignatureCountTempTable.username = MainTBL.username
    ) AS SoftwareSignatureCount

    ,
    (SELECT COUNT(DISTINCT
        LEFT(useragent, IIF((PATINDEX('%VDB%',useragent) -1) > 0, PATINDEX('%VDB%',useragent) -1, 0))
        +
        RIGHT(useragent, IIF((PATINDEX('%BPC%',useragent) -1) > 0, PATINDEX('%BPC%',useragent) -1, 0))
        )
        FROM TBL_iislog UseragentTempTable
        WHERE UseragentTempTable.username = MainTBL.username
    ) AS UserAgentCount

    ,
    (SELECT COUNT(DISTINCT clientip)
        FROM tbl_iislog AS IPTempTable
        WHERE IPTempTable.username = MainTBL.username
    ) AS IPCount

    ,
    (SELECT COUNT(clientip)
        FROM tbl_iislog ConnectionsTempTable
        WHERE ConnectionsTempTable.uri = '/version_checker.ver'
        AND ConnectionsTempTable.username = MainTBL.username
    ) AS Connections

    FROM TBL_iisLog AS MainTBL
    WHERE (username LIKE 'softgsg-%') OR (username LIKE 'sg-%')
    GROUP BY username HAVING COUNT(clientip) > 0
    ORDER BY SoftwareSignatureCount DESC, Connections DESC

我会感谢任何帮助我优化查询的建议。

1 个答案:

答案 0 :(得分:2)

首先,其他人评论并且确实......查询是一团糟,并且有很多字段级选择语句可以杀死性能,因为每个查询都是针对每个记录执行的,每个列都是您正在运行的。它们都基于当前用户的任何内容,恰好是某些标准的IIF()。我已经简化了,可能需要一点点调整,但是应该给你很大的帮助。

首先,作为HAVING子句基础的特定clientIP的计数与该列无关,但最好只用*表示无论特定列如何都存在记录。此外,在同一查询级别中,通过执行SUM(IIF())可以处理您的版本检查器“连接”计数。

对于您的签名条目,我在同一次运行中做了两次。首先,我通过MAX(IIF())获取一个标志,看看是否有任何记录没有'%SIG:+%'引用,然后还得到一个COUNT(与该签名发现截然不同)。这样,COUNT 减少任何非签名(这将被视为空格''因此,无论有多少非“SIG”条目将给予ACTUAL最终计数,所以最多扣除1个条目。

我也不知道为什么你有大量的IP数据> 0 ...从IIS日志文件中,一切都应该有一个客户端IP,所以我认为这是无关紧要的,因此被删除。

我还认为我严重简化了您的用户代理字段。如果找不到'%VDB%'或“%BPC%”条目,只需使用空字符串,否则获取每个字符串的子字符串上下文。

所以,现在只需一次通过表,所有聚合完成ONCE应该有所帮助。现在,索引。因为你在where子句中使用了LIKE,但好的是你不喜欢领先的'%',否则表明你正在寻找字符串“softgsg-”或“sg-”字符串中的任何地方,你是在字符串的LEADING部分寻找它,可以通过索引进行优化。

话虽如此,我会在

表上有一个复合索引

(用户名,clientip)

由于您的列处理255甚至MAX列宽文本,我不想仅仅为了使其成为“覆盖”索引而终止索引。

select
      PQ.*
   from 
      ( SELECT
              L.username,
              COUNT(*) TotalRecs,
              COUNT(DISTINCT clientip) DistinctIPs,
              SUM( IIF( L.uri = '/version_checker.ver', 1, 0 )) as Connections,
              MAX( IIF( PATINDEX('%SIG:+%', L.useragent) = 0, 1, 0 )) as NonSigEntries,
              COUNT( DISTINCT IIF( PATINDEX('%SIG:+%', L.useragent) = 0, ' ',
                                   SUBSTRING( L.useragent, PATINDEX('%SIG:+%', L.useragent) + 5, 36))) SoftwareSignatureCount,
              COUNT( DISTINCT 
                        IIF(PATINDEX('%VDB%', L.useragent) = 0, '', LEFT( L.useragent, PATINDEX('%VDB%', L.useragent) -1))
                      + IIF(PATINDEX('%BPC%', L.useragent) = 0, '', RIGHT( L.useragent, PATINDEX('%BPC%', L.useragent) -1))
                    ) AS UserAgentCount
           FROM 
              tbl_iislog L
           WHERE 
                 L.username LIKE 'softgsg-%'
              OR L.username LIKE 'sg-%'
           GROUP BY
              L.username ) PQ
   ORDER BY
      PQ.SoftwareSignatureCount - PQ.NonSigEntries DESC,
      PQ.Connections DESC

查询运行时,我根据查询提供的空表结构进行测试。由于顺序,我预先包装了查询(别名PQ for PreQuery),所以我会得到软件签名计数和非签名条目标志的最终值,然后连接,所以我没有重新复制相同的COUNT(复杂语句)到字段列表和顺序中...有些引擎允许您使用列结果名称别名与整个表达式。