存储过程提供与 tsql 不同的结果集,仅在某些服务器上

问题描述:

这是我昨天问的一个问题的后续:

This is a followup to a question I asked yesterday:

您是否曾让 SQL Server 2008 返回与 SQL Server 2000 不同的结果集?

我最初认为存储过程在 sql2000 和 sql2008 上给出了不同的结果,但我已经对问题进行了更多的缩小并消除了相当多的代码以将其归结为一个简单/可重现的问题.总结是,当作为 proc 运行时,一段 TSQL 返回不同的答案,即与 TSQL 一样运行的相同代码位但仅在我的客户端服务器上,而不是在我的任一测试服务器上.

where I originally thought that the stored procedure was giving differnt results on sql2000 versus sql2008, but I have done quite a bit more narrowing down of the problem and eliminated quite a bit of code to get it down to a simple/reproducible problem. The summary is, an piece of TSQL when run as a proc returns a different answer that the same bit of code running as as just TSQL, but only on my clients server, not on either of my test servers.

当我运行这个 TSQL 时:

When I run this TSQL:

DECLARE @PropertyID int 
DECLARE @PortfolioID    int 
DECLARE @StartDate  datetime 
DECLARE @EndDate    datetime 
DECLARE @AcctMethod tinyint 

SET @PropertyId=3555
--SET @PortfolioId = null
SET @StartDate= '3/1/2010'
SET @EndDate='2/28/2011'
SET @AcctMethod=1

DECLARE    @ErrorMsg    varchar(70)
DECLARE @ExclAcct tinyint

SET NOCOUNT ON
CREATE TABLE #IncomeStatement (
    PropertyID      int,
    GLAccountID     int,
    SubTotalAccountID   int,
    Debits          money,
    Credits         money,
    YTDDebits       money,
    YTDCredits      money,
    PZDebits        money,
    PZCredits       money,
    AccountType     tinyint
)

--Initialize Temporary Table
INSERT INTO #IncomeStatement(PropertyID, GLAccountID, SubTotalAccountID, AccountType, Debits, Credits, YTDDebits, YTDCredits, PZDebits, PZCredits)
SELECT PropertyID, ID, SubTotalAccountID, AccountType, 0, 0, 0, 0, 0, 0
FROM ChartOfAccounts
WHERE (PropertyID = @PropertyID OR @PropertyID Is Null)
    AND (@PortfolioID is null OR PropertyID in (select PropertyID from PortfolioProperty where PortfolioID=@PortfolioID))
    AND (Category > 3 or CashFlowCode <> 0)

--Period Activity
IF @AcctMethod = 1
    SET @ExclAcct = 0
ELSE
    SET @ExclAcct = 1

UPDATE Bal
SET 
    Debits = Debits +  D.TotDebit,
    Credits = Credits +  D.TotCredit
FROM #IncomeStatement Bal
    INNER JOIN (SELECT GLAccountID, Sum(Debit) AS TotDebit, Sum(Credit) AS TotCredit
            FROM GLTransaction GT
            WHERE (GT.PropertyID = @PropertyID OR @PropertyID Is Null)
                AND AccountingMethod <> @ExclAcct 
                AND Posted = 1
                AND TranDate >= @StartDate
                AND TranDate <= @EndDate
            GROUP BY GLAccountID) AS D
        ON BAL.GLAccountID = D.GLAccountID 

select * from #IncomeStatement  where GLAccountID=11153 
drop table    #IncomeStatement

但是,当我将上述代码转换为这样的存储过程时,我得到了 124.27 美元的借记金额:

I get a debit amount of $124.27, however, when I turn the above code into a stored procedure like this:

   CREATE Procedure [dbo].[sp_test]
    @PropertyID int = Null,
    @PortfolioID    int = Null,
    @StartDate  datetime = Null,
    @EndDate    datetime = Null,
    @AcctMethod tinyint = 1

AS


DECLARE    @ErrorMsg    varchar(70)
DECLARE @ExclAcct tinyint

SET NOCOUNT ON
CREATE TABLE #IncomeStatement (
    PropertyID      int,
    GLAccountID     int,
    SubTotalAccountID   int,
    Debits          money,
    Credits         money,
    YTDDebits       money,
    YTDCredits      money,
    PZDebits        money,
    PZCredits       money,
    AccountType     tinyint
)

--Initialize Temporary Table
INSERT INTO #IncomeStatement(PropertyID, GLAccountID, SubTotalAccountID, AccountType, Debits, Credits, YTDDebits, YTDCredits, PZDebits, PZCredits)
SELECT PropertyID, ID, SubTotalAccountID, AccountType, 0, 0, 0, 0, 0, 0
FROM ChartOfAccounts
WHERE (PropertyID = @PropertyID OR @PropertyID Is Null)
    AND (@PortfolioID is null OR PropertyID in (select PropertyID from PortfolioProperty where PortfolioID=@PortfolioID))
    AND (Category > 3 or CashFlowCode <> 0)

--Period Activity
IF @AcctMethod = 1
    SET @ExclAcct = 0
ELSE
    SET @ExclAcct = 1

UPDATE Bal
SET 
    Debits = Debits +  D.TotDebit,
    Credits = Credits +  D.TotCredit
FROM #IncomeStatement Bal
    INNER JOIN (SELECT GLAccountID, Sum(Debit) AS TotDebit, Sum(Credit) AS TotCredit
            FROM GLTransaction GT
            WHERE (GT.PropertyID = @PropertyID OR @PropertyID Is Null)
                AND AccountingMethod <> @ExclAcct 
                AND Posted = 1
                AND TranDate >= @StartDate
                AND TranDate <= @EndDate
            GROUP BY GLAccountID) AS D
        ON BAL.GLAccountID = D.GLAccountID 

select * from #IncomeStatement  where GLAccountID=11153 
drop table    #IncomeStatement

然后像这样执行它:

EXEC sp_test @PropertyID=3555, @StartDate='03/01/2010', @EndDate='02/28/2011'

我的借方金额为 248.54 美元,正好是应有的两倍.

I get a debit amount of $248.54, which is exactly double what it should be.

我真的被难住了.更奇怪的是,如果我备份这个数据库,然后将它复制到运行 sql2000 的 win2003 服务器或运行 SQL2008R2 的 win2008 服务器,它在这两种情况下都能正常工作.因此,似乎是服务器或数据库设置导致了问题,但已经没有什么可以检查的了 - 希望有新的眼光可以指出我遗漏的一些明显的东西.>

I am really stumped. The odder thing, is that if I backup this database, and then copy it to either my win2003 server running sql2000, or my win2008 server running SQL2008R2, it works correctly in both cases. So, it seems it is a server or database setting that is causing the problem, but have run out of things to check - hoping a fresh set of eyes can point out something obvious I am missing.

好的,这是我的修复 - 它绝对不能解释最初的问题,但这是我所做的:

OK, here is my fix - it absolutely doesn't not explain the original problem, but this is what I did:

每当我遇到参数嗅探"性能问题时,为了解决我为所有参数声明局部"变量,将这些参数分配给这些变量,然后仅使用其余过程中的局部变量,像这样:

Whenever I have a "parameter sniffing" performance issue, in order to solve that I declare 'local' variables for all parameters, assign those parameters to those variables, and then use only the local variables in the rest of the proc, like this:

  ALTER Procedure [dbo].[rptDateIncomeStatementPlusCash]
        @PropertyID int = Null,
        @PortfolioID    int = Null,
        @StartDate  datetime = Null,
        @EndDate    datetime = Null,
        @AcctMethod tinyint = 1
    AS
      DECLARE   @xPropertyID    int 
      DECLARE   @xPortfolioID   int 
      DECLARE   @xStartDate datetime 
      DECLARE   @xEndDate   datetime 
      DECLARE   @xAcctMethod    tinyint 

      SET @xPropertyID= @PropertyId
      SET @xPortfolioId = @PortfolioId
      SET @xStartDate = @StartDate
      SET @xEndDate = @EndDate
      SET @xAcctMethod = @AcctMethod

相似之处在于,当参数嗅探是一个问题时,您可以通过 MGMT Studio 运行存储的 proc 并获得比将其作为 SQL 运行更好的性能,并且更改(如上面的那些)通常可以修复它.

the similarity is that when parameter sniffing is an issue, you can run a stored proc thru MGMT studio and get better performance than running it as an SQL, and changes (like those above), usually fix it.

就我而言,我发现直接 TSQL 与执行 proc 之间存在差异(尽管它与性能无关),我尝试了一下 - 并且很快就起作用了;我希望我有一个更好的解释,因为老实说,我觉得在运行几乎相同的代码时 SQL Server 有时会给出不一致的结果是很可怕的.

In my case I was seeing a difference between straight TSQL versus executing the proc(although it was not performance related), I gave it a try - and presto it worked; I wish I had a better explanation, because quite honestly I find it scary to think that SQL server will on occassion give inconsistent results when running nearly identical code.

我确实找到了来自 MS 的这个公告,关于一个类似但不同的问题,至少确实证实了这一点情况下,SQL 服务器可能会给你错误的答案,并且 这个相关的错误报告带有这个关键词:

I did find this bulletin from MS about a similar but different problem, that at least does confirm that under the right circumstances, SQL server may give you bad answers, and this related bug report with this key phrase:

描述:每场两场对过程 P 的多次调用改变参数值.这过程 P 执行一个查询静态数据,有时带有 OPTION(重新编译)有时没有.

Description: Two sessions each make many calls to a procedure P with changing parameter values. The procedure P executes one query against static data, sometimes with OPTION (RECOMPILE) and sometimes without.

偶尔 P 会给出错误的结果(对于下面的再现,这通常发生大约 1/2% - 1% 的时间).当P的结果错误时,P返回预期数字的 0 或两倍行.

Occasionally P gives incorrect results (for the repro below, this typically happens about 1/2% - 1% of the time). When P's results are wrong, P returns either 0 or twice the expected number of rows.

昨天有人发表了关于参数嗅探的评论作为一种可能性,但无论出于何种原因他们删除了他们的评论(或答案)所以我不能相信他们的提示.