【问题标题】:optimized way to get row count from a query contains large amount of data从查询中获取行数的优化方法包含大量数据
【发布时间】:2014-04-26 06:30:59
【问题描述】:

我正在使用下面的查询来返回行数进行分页,它工作正常但需要很长时间才能返回,因为所有表都有数百万条记录。 目前它需要 7 秒才能返回行数,任何人都可以帮助我快速返回它。

我也尝试过使用#table 和@table 进行相同的查询,它们都很慢。 查询是

WITH cte_rowcount 
     AS (SELECT p.policyid 
         FROM   resident (nolock) r 
                INNER JOIN resident_policy (nolock) rp 
                        ON r.residentid = rp.residentid 
                INNER JOIN policy (nolock) p 
                        ON p.policyid = rp.policyid 
                --INNER JOIN PolicySource (NOLOCK) psourse ON p.PolicySourceID = psourse.PolicySourceId 
                INNER JOIN policy_locations (nolock) pl 
                        ON pl.policyid = p.policyid 
                INNER JOIN location (nolock) l 
                        ON pl.locationid = l.locationid 
                --INNER JOIN Policy_Status (NOLOCK) ps ON ps.PolicyStatusId = p.PolicyStatusId 
                INNER JOIN property (nolock) pr 
                        ON pr.propertyid = l.propertyid 
         --INNER JOIN dbo.States (NOLOCK) s ON s.StateId = pr.StateId 
         WHERE  r.primary_resident = 0x1 
                AND ( ( @ResidentFirstName IS NULL ) 
                       OR R.firstname LIKE @ResidentFirstName + '%' ) 
                AND ( ( @ResidentLastName IS NULL ) 
                       OR R.firstname LIKE @ResidentLastName + '%' ) 
                AND ( @PropertyAddress IS NULL 
                       OR pr.address LIKE @PropertyAddress + '%' ) 
                AND ( @Policynumber IS NULL 
                       OR p.policynumber LIKE @Policynumber + '%' ) 
                AND ( @LocationAddress IS NULL 
                       OR l.address2 LIKE @LocationAddress + '%' ) 
                AND ( @City IS NULL 
                       OR pr.city LIKE @City + '%' ) 
                AND ( @ZipCode IS NULL 
                       OR pr.zipcode = @ZipCode ) 
                AND ( @StateId IS NULL 
                       OR pr.stateid = @StateId ) 
                AND ( @PolicyStatusId IS NULL 
                       OR p.policystatusid = @PolicyStatusId )) 
SELECT @rowcount = Count(*) 
FROM   cte_rowcount 

【问题讨论】:

  • 您想从包含数百万行的 6 个表的连接中计算行数,其中任何一个都有条件。即使有良好的索引,我也不会期待奇迹。这些看起来都像外键,如果这是真的,你应该拥有你需要的大部分索引。您应该在要过滤的列上添加索引(因为您匹配值的开头,这可能会有所帮助)。您还可以优化 SQL 动态地省略不需要的连接和过滤器(删除那些 @IS NULL 语法)。
  • 你似乎也有一个错误,在那里你使用 @ResidentLastName 来匹配 R.firstname 列。

标签: sql sql-server tsql rowcount


【解决方案1】:

我会说查看索引,但它可能不会有太大帮助,因为 a)您可能已经这样做了,并且 b)您无法通过这种查询进行搜索,只能进行扫描。

我们的想法是摆脱这些 OR,让优化器产生合理的计划。

有两种选择。

不知道哪个版本的 SQL Server 存在问题,但如果是 SQL 2008 SP1 CU5 (10.0.2746) 或更高版本,或者 SQL 2008 R2 CU1 (10.50.1702) 或更高版本,或者比这更高的版本,在查询中添加option (recompile)。这应该会产生更好的计划,使用相关索引上的搜索。

然而,这会给每次执行增加一些重新编译开销,所以也许第二个选项更好。

您可以将查询重写为动态查询,并在优化器甚至看到查询之前消除 NULL 参数。我试图重写您的查询,没有您的数据所以无法对其进行测试,并且其中可能存在一些错误,但您仍然会明白我的意图。我不得不猜测数据类型。 (顺便说一句,SELECT p.policyid 有什么具体原因吗?)

这里是:

declare @qry nvarchar(4000), @prms nvarchar(4000);
set @qry = N'
SELECT count(*)
         FROM   resident (nolock) r 
                INNER JOIN resident_policy (nolock) rp 
                        ON r.residentid = rp.residentid 
                INNER JOIN policy (nolock) p 
                        ON p.policyid = rp.policyid 
                INNER JOIN policy_locations (nolock) pl 
                        ON pl.policyid = p.policyid 
                INNER JOIN location (nolock) l 
                        ON pl.locationid = l.locationid 
                INNER JOIN property (nolock) pr 
                        ON pr.propertyid = l.propertyid 
         WHERE  r.primary_resident = 0x1 '
if @ResidentFirstName IS NOT NULL
    set @qry = @qry + ' AND R.firstname LIKE @ResidentFirstName + ''%'''  
if @ResidentLastName IS NOT NULL 
    set @qry = @qry + ' AND R.firstname LIKE @ResidentLastName + ''%'''
if @PropertyAddress IS NOT NULL 
    set @qry = @qry + ' AND pr.address LIKE @PropertyAddress + ''%''' 
if @Policynumber IS NOT NULL 
    set @qry = @qry + ' AND p.policynumber LIKE @Policynumber + ''%''' 
if @LocationAddress IS NOT NULL 
    set @qry = @qry + ' AND l.address2 LIKE @LocationAddress + ''%''' 
if @City IS NOT NULL 
    set @qry = @qry + ' AND pr.city LIKE @City + ''%''' 
if @ZipCode IS NOT NULL 
    set @qry = @qry + ' AND pr.zipcode = @ZipCode'
if @StateId IS NOT NULL 
    set @qry = @qry + ' AND pr.stateid = @StateId'
if @PolicyStatusId IS NOT NULL 
    set @qry = @qry + ' AND p.policystatusid = @PolicyStatusId'


set @prms = N'@PolicyStatusId int, @StateId int, @ZipCode int,
@City varchar(50), @LocationAddress varchar(50), @Policynumber varchar(50), 
@PropertyAddress varchar(50), @ResidentLastName varchar(50), @ResidentFirstName varchar(50)'

exec sp_executesql 
@qry, 
@prms,
@PolicyStatusId = @PolicyStatusId, @StateId = @StateId, @ZipCode = @ZipCode,
@City = @City, @LocationAddress = @LocationAddress, 
@Policynumber = @Policynumber, @PropertyAddress = @PropertyAddress, 
@ResidentLastName = @ResidentLastName, @ResidentFirstName = @ResidentFirstName

如果您检查执行计划,您将看到索引搜索,前提是您在 WHERE 和 JOIN 列上有非聚集索引。

此外,计划将被缓存,每个参数组合一个。

【讨论】:

  • 感谢您的帮助,这确实节省了我的时间,现在我的查询时间比以前减少了 50%
  • 我期待更多的收获。好了,现在是检查执行计划的时候了(首先查找粗箭头及其源运算符,然后是查找)。
【解决方案2】:

这很难回答,因为有大量数据可能会发生很多事情。

在连接方面,这应该表现良好。 如果此查询只是在这里执行计数,那么我可以建议您直接执行 SELECT count('x') 而不使用 CTE 和不使用 (nolock)

SELECT @rowcount = count('x') as rc
FROM   
    resident r 
    INNER JOIN resident_policy rp 
        ON r.residentid = rp.residentid 
    INNER JOIN policy p 
        ON p.policyid = rp.policyid 
    INNER JOIN policy_locations pl 
        ON pl.policyid = p.policyid 
    INNER JOIN location l 
        ON pl.locationid = l.locationid 
    INNER JOIN property pr 
        ON pr.propertyid = l.propertyid 
WHERE  
    r.primary_resident = 0x1 
    AND ( ( @ResidentFirstName IS NULL ) 
        OR R.firstname LIKE @ResidentFirstName + '%' ) 
    AND ( ( @ResidentLastName IS NULL ) 
        OR R.firstname LIKE @ResidentLastName + '%' ) 
    AND ( @PropertyAddress IS NULL 
        OR pr.address LIKE @PropertyAddress + '%' ) 
    AND ( @Policynumber IS NULL 
        OR p.policynumber LIKE @Policynumber + '%' ) 
    AND ( @LocationAddress IS NULL 
        OR l.address2 LIKE @LocationAddress + '%' ) 
    AND ( @City IS NULL 
        OR pr.city LIKE @City + '%' ) 
    AND ( @ZipCode IS NULL 
        OR pr.zipcode = @ZipCode ) 
    AND ( @StateId IS NULL 
        OR pr.stateid = @StateId ) 
    AND ( @PolicyStatusId IS NULL 
        OR p.policystatusid = @PolicyStatusId )

如果此 CTE 用于行计数和从 CTE 检索数据,请确保您只检索相关页面的数据(只有 20 个带有 ROWCOUNT() as RCRC > 0 AND RC <= 20 的元素)

在数据库方面,您可以检查是否有所有连接子句的索引。看起来只有 PK 所以他们已经有了索引。请确保,您在连接的列上有索引。

如果您仍然遇到问题,请使用“实时执行计划”功能查看到底发生了什么。

LIKE 条件可能会成为性能杀手,具体取决于文本大小和数据库内容。您可以考虑使用 COLLECTION 来存储您的文本并在文本比较方面有所收获。

【讨论】:

    【解决方案3】:

    有一些通用说明:

    • 所有外键列上创建非聚集索引
    • 在 primary_resident 列上创建非聚集索引
    • 在运行查询时包含实际执行计划并查看哪个部分在浪费时间
    • 首先放置更有可能是false的语句
    • 当您运行查询时,SQL 服务器会建议您一些提示,也请尝试一下

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多