无法进一步调优数据库;接下来是什么?

Mar*_*son 5 performance sql-server sql-server-2008-r2 query-performance performance-tuning

我们有一个供应商提供的应用程序。它正在支持中,我们正在与他们的开发人员交谈(我们拥有他们最大的数据库大小一个数量级),但与此同时,我们有以下每天运行数千次的查询,只有部分更改了ABCDEFG12345where条款中。where's 中的几乎所有内容都是可定制的,因为它是由他们的主要工作搜索生成的查询。

dJobs 表中大约有 500,000 条记录,连接表中的记录数也差不多:

select * from (
    select   top       20 * from (
        select  top       20 
            (       --Start Count
                select count(*) from dJobs
                left join dClients on cltClientID = jobClientId 
                left join dJobStatus ON jbsID = jobJobStatus    

                where ((jobSupervisor=         -1.00000000  or jobCoordinator=         -1.00000000 )  OR 1=1 ) 

                and 
                (
                (jobjobStatus=0 OR (0=0  AND 0=0)) 
                AND (jbsType=0 OR (0=0  AND 0=0)) 
                AND (jobPriority=0 OR (0=0  AND 0=0)) 
                AND (jobEventID=0 OR (0=0 AND 0=0))
                AND (jobSiteCode='' OR (''='' AND ''=''))
                AND (jobjobStatus IN (
                    select jbsid from djobstatusgroupmapping where jsgid = 0
                    )               
                 OR (0=0 AND 0=0))
                   AND jobDateCreated BETWEEN '2004-06-10' AND '2014-06-10'    AND jobBookedDate BETWEEN '1901-01-01 00:00:00' AND '2024-06-10 23:59:00'    
        and
        (('ABCDEFG12345'<>'' 
            AND
            ( 
                (1 = 1 AND 
                    (
                        jobWorkOrderNo like '%ABCDEFG12345%' OR
                        jobSiteName like '%ABCDEFG12345%' OR
                        jobSiteLocationBuilding like '%ABCDEFG12345%' OR
                        jobSiteAddress like '%ABCDEFG12345%' OR
                        jobSiteSuburb like '%ABCDEFG12345%' OR
                        jobSitePostcode like '%ABCDEFG12345%' OR
                        jobWorkToDo like '%ABCDEFG12345%' OR
                        jobSiteClient like '%ABCDEFG12345%' OR
                        jobSiteContact like '%ABCDEFG12345%' OR
                        jobSiteContactPhone like '%ABCDEFG12345%' OR
                        jobSiteContactPhone2 like '%ABCDEFG12345%'
                    )
                ) 
            OR 
                (1 = 1 AND 
                    (
                        cltClientName like '%ABCDEFG12345%' OR
                        cltDivision like '%ABCDEFG12345%' OR
                        cltAddress1 like '%ABCDEFG12345%' OR
                        cltAddress2 like '%ABCDEFG12345%' OR
                        rtrim(cltAddress1)+' '+rtrim(cltAddress2) like '%ABCDEFG12345%' OR
                        cltSuburb like '%ABCDEFG12345%' OR
                        cltPostCode like '%ABCDEFG12345%' OR
                        cltState like '%ABCDEFG12345%' OR
                        cltTelephone like '%ABCDEFG12345%' OR
                        cltContact like '%ABCDEFG12345%' OR
                        cltMobile like '%ABCDEFG12345%' OR
                        cltEmail like '%ABCDEFG12345%'
                    )
                ) 
            )
        ) 
        OR 'ABCDEFG12345'='')                
                )   --End Count
            )  as vRecCount, 
            jobID,
            jobWorkOrderNo,
            jobSequence,                        
            jobSiteClient,
            jobSiteStreetNumber,
            jobSiteAddress,
            jobSiteSuburb,
            jobSiteState,
            jobSitePostcode,                
            jobDateofLoss,
            jobDateofLossTime,
            jobDateCreated,
            jobTargetDate,
            jobClientID,            
            jobPriority,
            jobJobStatus,
            jobEventID,
            regFullname,
            case 
            when (not jobSupervisor=         -1.00000000 ) and jobCoordinator=         -1.00000000  then 1 else 
            case when (jobSupervisor=         -1.00000000 ) and (jobCoordinator=0) then 2 else   
            case when (jobSupervisor=         -1.00000000 ) and (not jobCoordinator=         -1.00000000 ) then 3 else 0
            end end end as 'Coordinator',
            jobSupervisor,
            jobBookedDate,
            jobLat,
            jobLong,
            jobClaimAssistRequest,
            jobBooked,
            jobSiteContact,
            jobSiteContactPhone2,
            jobPCM
        from dJobs 
        left join dClients on cltClientID = jobClientId     
        left join dUsers on regUserId = jobCoordinator  
        left join dJobStatus ON jbsID = jobJobStatus    

        where ((jobSupervisor=         -1.00000000  or jobCoordinator=         -1.00000000 )  OR 1=1 ) 

        and 
                (
                (jobjobStatus=0 OR (0=0  AND 0=0)) 
                AND (jobPriority=0 OR (0=0  AND 0=0)) 
                AND (jbsType=0 OR (0=0  AND 0=0)) 
                AND (jobEventID=0 OR (0=0 AND 0=0))
                AND (jobSiteCode='' OR (''='' AND ''=''))
                AND (jobjobStatus IN (
                    select jbsid from djobstatusgroupmapping where jsgid = 0
                    )               
                 OR (0=0 AND 0=0))                      
                )
                  AND jobDateCreated BETWEEN '2004-06-10' AND '2014-06-10'    AND jobBookedDate BETWEEN '1901-01-01 00:00:00' AND '2024-06-10 23:59:00'    
        and
        (('ABCDEFG12345'<>'' 
            AND
            ( 
                (1 = 1 AND 
                    (
                        jobWorkOrderNo like '%ABCDEFG12345%' OR                 
                        jobSiteName like '%ABCDEFG12345%' OR
                        jobSiteLocationBuilding like '%ABCDEFG12345%' OR
                        jobSiteAddress like '%ABCDEFG12345%' OR rtrim(jobSiteStreetNumber)+' '+rtrim(jobSiteAddress)  LIKE '%ABCDEFG12345%' OR
                        jobSiteSuburb like '%ABCDEFG12345%' OR
                        jobSitePostcode like '%ABCDEFG12345%' OR
                        jobWorkToDo like '%ABCDEFG12345%' OR
                        jobSiteClient like '%ABCDEFG12345%' OR
                        jobSiteContact like '%ABCDEFG12345%' OR
                        jobSiteContactPhone like '%ABCDEFG12345%' OR
                        jobSiteContactPhone2 like '%ABCDEFG12345%'
                    )
                ) 
            OR 
                (1 = 1 AND 
                    (
                        cltClientName like '%ABCDEFG12345%' OR
                        cltDivision like '%ABCDEFG12345%' OR
                        cltAddress1 like '%ABCDEFG12345%' OR
                        cltAddress2 like '%ABCDEFG12345%' OR
                        rtrim(cltAddress1)+' '+rtrim(cltAddress2) like '%ABCDEFG12345%' OR
                        cltSuburb like '%ABCDEFG12345%' OR
                        cltPostCode like '%ABCDEFG12345%' OR
                        cltState like '%ABCDEFG12345%' OR
                        cltTelephone like '%ABCDEFG12345%' OR
                        cltContact like '%ABCDEFG12345%' OR
                        cltMobile like '%ABCDEFG12345%' OR
                        cltEmail like '%ABCDEFG12345%'
                    )
                ) 
            )
        ) 
        OR 'ABCDEFG12345'='')
             order by  jobid desc ,  jobDateCreated desc  
    ) as newtbl   order by  jobid asc ,  jobDateCreated asc 
 ) as newtbl_2 order by  jobid desc, jobDateCreated desc
Run Code Online (Sandbox Code Playgroud)

SQL Server 数据库优化顾问对我没有任何建议 - 所以看起来开发人员在为查询创建适当的索引和统计信息方面做得很好。

直到他们最终能够重构事情,这可能需要 6 个多月的时间,我们还剩下什么?只是在 SQL 服务器上投入更多资金(SSD、更多 RAM,也许是用于分区的企业版)?

交易成本细分为:

  • 总 CPU 时间(毫秒): 12,237.70
  • # 总逻辑 IO: 2,331,089
  • #平均。逻辑IO: 2,331,089.00
  • # 逻辑读取: 2,331,089

在 VMWare 虚拟机内运行的 SQL Server 2008 R2 Standard。


下面执行计划中有趣的部分。大多数时间花在聚集索引扫描上。

在此处输入图片说明


抱歉耽搁了; 以下统计数据来自我们的临时服务器,它的记录数量是生产记录数量的 1/10,但也在较低级别的硬件上。执行计划 XML , STATISTICS IO:

Table 'dClients'. Scan count 5, logical reads 705780, physical reads 9, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'dJobs'. Scan count 10, logical reads 23386, physical reads 16, read-ahead reads 10601, lob logical reads 186928, lob physical reads 578, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'dUsers'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Run Code Online (Sandbox Code Playgroud)

wBo*_*Bob 10

如果不重构以使用全文函数(CONTAINS、FREETEXT 或它们的表等效项),全文将无济于事。它也不适用于领先的通配符。Hacks 是可用的,但基本上你将很难为全文编写语义等效的查询。未来考虑重新设计全文,它具有词干( run、runner、running )和同义词库( jogger ),可以比两个通配符更好地为您的搜索服务。

除非您受内存限制,否则 SSD 不太可能对您有所帮助。您的表(只有 50 万条记录)可能大部分时间都在内存中。你能确认dJobs表的大小和服务器RAM吗?

企业版可以帮助 64GB RAM/4 个插槽或 16 个内核中较少者的限制增加到 8 个,但您将需要一个非常强大的盒子才能注意到差异。例如,4 真的意味着您可以拥有 4 个四核处理器,总共 16 个内核,启用 HT 后,您已经拥有 32 个逻辑处理器。对于这种类型的 OLTP 机器,一般推荐的服务器 maxdop 无论如何都是 8。我认为这不太可能受益,因为您的查询有更多基本问题,但您永远不知道。

非聚集索引(特别是在 dJobs 上)不太可能有帮助,因为查询在 SELECT 中有很多来自该表的列,而在 WHERE 子句中有很多条件。非聚集索引必须非常广泛才能覆盖它实际上是聚集索引的副本,因此维护成本过高。由于查询按 jobID DESC 排序,我考虑了降序索引,但尚未对此进行试验。

分区(仅限企业版)确实是一个很棒的功能,但同样不太可能对您有所帮助。我对 dbo.dJobs.jobJobStatus 列上的分区进行了快速调查,例如,我想您在任何时候都只有一小部分“活动”作业,例如,500,000 条记录中的几百甚至几千。OR OR OR 方法可能会取消分区消除。多个分区的并行扫描也是企业功能:

这可能会起作用:

SELECT TOP 20 *
FROM dJobs
    LEFT JOIN dClients on cltClientID = jobClientId
    LEFT JOIN dUsers on regUserId = jobCoordinator
    LEFT JOIN dJobStatus ON jbsID = jobJobStatus
WHERE
    (
    jobjobStatus IN ( SELECT jbsid FROM djobstatusgroupmapping WHERE jsgid = 0 )
    )
ORDER BY jobID DESC
Run Code Online (Sandbox Code Playgroud)

这可能行不通:

SELECT TOP 20 *
FROM dJobs
    LEFT JOIN dClients on cltClientID = jobClientId
    LEFT JOIN dUsers on regUserId = jobCoordinator
    LEFT JOIN dJobStatus ON jbsID = jobJobStatus
WHERE
    (
    jobjobStatus IN ( SELECT jbsid FROM djobstatusgroupmapping WHERE jsgid = 0 )
    OR ( 0=0 ) OR ( 0=0 )   --<< this 'OR always true' means 'get the whole table'
    )
ORDER BY jobID DESC
Run Code Online (Sandbox Code Playgroud)

这使我进入查询。OR OR OR 方法基本上意味着“总是得到整个表”。TOP 20 掩盖了这个设计问题。TOP 也可能将计划推向了 Nested Loops,Jon 认为这是可疑的。对于这个噩梦般的“扫描所有列”构造的查询,对我来说也很突出的是,您基本上拥有相同查询(以及表)的两份副本,一份用于计数,一份用于结果集。例如,如果数据进入中间表并从那里完成计数,这可能会更有效。

最后,这让我想到了唯一真正可以帮助您的事情(无需大规模重构代码):数据删除或归档。如前所述,我想您在任何时候都只有一小部分“活跃”的工作。将“不活动”的部分划分到不同的表中。在两个表的顶部创建一个视图以进行报告。设置一个夜间工作来复制旧记录。

主表中只有几千个活动作业很可能会改变您的查询性能。

一些推荐阅读:

Erland Sommarskog 关于这些“搜索所有列”的文章在 T-SQL 中 查询 动态搜索条件http://www.sommarskog.se/dyn-search-2008.html

查询多列(全文搜索) http://technet.microsoft.com/en-us/library/ms142488(v=sql.105).aspx

我希望这有帮助!


Ava*_*rkx 1

我认为你应该仍然能够降低你的读数;您是否尝试过将表上的聚集索引扫描转换为非聚集索引扫描dJobs?很难仅从图片中准确说出您需要如何做到这一点(jobBookedDate至少jobDateCreated),并且就整体性能而言,它可能会以任何一种方式进行,但如果您不能或这样做,并且情况会变得更糟,那么分区或更快的 IO 子系统看起来它们将是您唯一的选择。