You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server同一存储过程两库性能差异求助(附查询及IO统计)

排查与优化建议:相同查询在不同数据库性能差异问题

从你提供的IO统计和执行计划信息来看,核心问题在于慢数据库中Installment表的逻辑读远超快数据库(284万 vs 10万+),且这个增长幅度远超过返回行数的比例(6.4万 vs 5千),说明执行计划的效率差异是关键。以下是具体的排查步骤和优化建议:

一、优先排查统计信息一致性

SQL Server的查询优化器严重依赖准确的统计信息来生成高效执行计划。两个数据库可能因为统计信息过时/不准确,导致优化器选择了不同的执行策略:

  • 对慢数据库的相关表更新统计信息,建议使用全扫描保证准确性:
    UPDATE STATISTICS dbo.Installment WITH FULLSCAN;
    UPDATE STATISTICS dbo.Plot WITH FULLSCAN;
    UPDATE STATISTICS dbo.InstallmentPlan WITH FULLSCAN;
    
  • 更新后重新执行查询,观察性能是否改善。

二、检查索引配置是否一致

虽然存储过程/查询相同,但两个数据库的索引可能存在差异,这是常见的性能差异原因:

  1. 针对子查询的优化:你的查询中包含一个子查询InstallmentStartDate,用于获取每个PlotId的第0期分期的最早DueDate。可以为Installment表创建以下索引,让子查询直接通过索引获取数据,避免全表扫描:
    CREATE NONCLUSTERED INDEX IX_Installment_InstallmentOrder_PlotId
    ON dbo.Installment (InstallmentOrder, PlotId)
    INCLUDE (DueDate);
    
  2. 主查询的覆盖索引:主查询涉及Installment表的多个列,创建覆盖索引可以避免昂贵的键查找操作,减少逻辑读:
    CREATE NONCLUSTERED INDEX IX_Installment_PlotId_IncludeAll
    ON dbo.Installment (PlotId)
    INCLUDE (Id, InstallmentNo, InstallmentOrder, Amount, DueDate, AmountPaid, PaidOn, SurchargePaid, surchargePaidOn, PartialInstallmentId, Is_Lumpsum);
    
  3. 对比两个数据库的索引结构,确保慢数据库的索引配置和快数据库一致。

三、优化自定义标量函数的性能

查询中使用了GetInstPlanDueDate和CalculateSurchargableDays两个标量函数,这类函数在SQL Server中是逐行执行的,当返回行数达到6万+时,会带来巨大的性能开销:

  • 将标量函数重构为内联表值函数(Inline TVF),内联TVF会被查询优化器展开,与主查询合并执行,避免逐行调用的开销。例如:
    CREATE FUNCTION dbo.GetInstPlanDueDate_Inline
    (
        @StartDate DATE,
        @DueDate DATE,
        @PaidOn DATE,
        @InstSurchargeDueMonth INT,
        @InstallmentOrder INT,
        @Is_Lumpsum BIT,
        @InstallmentStartDueDate DATE,
        @PlotId INT
    )
    RETURNS TABLE
    AS
    RETURN
    (
        -- 原函数的逻辑在这里实现,返回单一行的结果
        SELECT /* 计算后的Sutcharge_Start_From值 */ AS Sutcharge_Start_From
    );
    
  • 重构后在查询中用CROSS APPLY调用这个函数,替代原有的标量函数调用。

四、避免重复计算

查询中的case语句重复调用了CalculateSurchargableDays函数,这会导致同一行数据被计算两次。可以用CTE或子查询预先计算该值,减少重复计算:

WITH InstallmentCTE AS (
    SELECT 
        i.*,
        p.PlotNo, p.PhaseId, p.InstallmentPlanId,
        ip.StartDate, ip.InstSurchargeDueMonth, ip.InstSurchargePercentage,
        isd.DueDate AS InstallmentStartDueDate,
        -- 预先计算一次Surcharge起始日期和可计息天数
        dbo.GetInstPlanDueDate(ip.StartDate, ISNULL(i.DueDate, GETDATE()), ISNULL(i.PaidOn, GETDATE()), ip.InstSurchargeDueMonth, i.InstallmentOrder, ISNULL(i.Is_Lumpsum, 0), isd.DueDate, i.PlotId) AS Sutcharge_Start_From,
        dbo.CalculateSurchargableDays(i.DueDate, ISNULL(i.PaidOn, GETDATE()), dbo.GetInstPlanDueDate(ip.StartDate, ISNULL(i.DueDate, GETDATE()), ISNULL(i.PaidOn, GETDATE()), ip.InstSurchargeDueMonth, i.InstallmentOrder, ISNULL(i.Is_Lumpsum, 0), isd.DueDate, i.PlotId), i.InstallmentOrder) AS SurchargableDays
    FROM dbo.Installment i
    INNER JOIN dbo.Plot p ON i.PlotId = p.Id
    INNER JOIN dbo.InstallmentPlan ip ON p.InstallmentPlanId = ip.Id
    INNER JOIN (
        SELECT PlotId, MIN(DueDate) AS DueDate 
        FROM dbo.Installment 
        GROUP BY InstallmentOrder, PlotId 
        HAVING (InstallmentOrder = 0)
    ) isd ON isd.plotid = i.plotid
    WHERE p.InstallmentPlanId > 0
)
SELECT 
    Id, InstallmentNo, InstallmentOrder, Amount, DueDate, AmountPaid, PaidOn, PlotId,
    SurchargePaid, surchargePaidOn, PartialInstallmentId, Is_Lumpsum,
    PlotNo, PhaseId, InstallmentPlanId, StartDate, InstSurchargeDueMonth,
    Sutcharge_Start_From,
    SurchargableDays AS days,
    CASE ISNULL(SurchargePaid,0) 
        WHEN 0 THEN SurchargableDays * (ISNULL(Amount, 0) * (InstSurchargePercentage / 365.0 / 100))
        ELSE ISNULL(Surcharge,0) 
    END AS surcharge_calculated,
    CASE ISNULL(AmountPaid,0) WHEN 0 THEN 0 ELSE 0 END as Payment_Status,
    InstallmentOrder%6 as t
FROM InstallmentCTE;

五、对比执行计划细节

仔细对比快/慢查询的实际执行计划,重点关注:

  • Installment表的访问方式:是索引Seek还是Scan?慢查询是否出现了全表扫描?
  • 连接运算符的选择:快查询用嵌套循环,慢查询是否用了哈希匹配/合并连接?(数据量不同时优化器会选择不同连接方式,但如果统计信息不准,可能选错)
  • 预估行数与实际行数的差异:如果差异过大,说明统计信息需要更新。

内容的提问来源于stack exchange,提问作者khawar abbasi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 13:07:44