跨服务器SQL查询结果不一致问题求助
执行一条跨服务器SQL查询时,每次返回的行数几乎都不一致,但未出现任何报错。已尝试在查询涉及的所有服务器上执行该语句,也在一台关联了这两台服务器的无关第四台服务器上测试过。单独运行每个CTE(公共表表达式)时结果始终一致,仅查询的最后部分会出现结果不一致的情况。
查询语句
with customerOrderMatches as ( select SAPOX.docentry ,SAPO.cardcode as 'O CC' ,SAPCMS.CardCode+'-'+SAPCMS.Address as 'OrigShipToID' ,SAPCMB.CardCode+'-'+SAPCMB.Address as 'OrigCustID' from [Server1].[BowDB].[dbo].ORDR SAPO join [Server1].[BowDB].[dbo].RDR12 SAPOX on SAPO.docentry=SAPOX.docentry left outer join [Server1].[BowDB].[dbo].CRD1 SAPCMS --ship to match on SAPCMS.cardcode=SAPO.cardcode and SAPCMS.Address=SAPO.Shiptocode and SAPCMS.AdresType='S' and SAPCMS.Street=SAPOX.StreetS left outer join [Server1].[BowDB].[dbo].CRD1 SAPCMB --bill to match on SAPCMB.cardcode=SAPO.cardcode and SAPCMB.Address=SAPO.PayToCode and SAPCMB.AdresType='B' and SAPCMB.Street=SAPOX.StreetB where SAPO.cardcode NOT IN ( '1001', '1002', '1003' ) AND SAPO.canceled = 'N' ), customerRank as ( SELECT rtrim(C.custid) AS 'custid' ,COUNT(SLShipper.ShipperID) as 'totalShippers' ,Row_number() OVER (ORDER BY COUNT(SLShipper.ShipperID) DESC) AS 'customerRank' FROM [MLSQL12].[SLapplication15].dbo.customer C left outer join [MLSQL12].[SLapplication15].dbo.SOShipHeader SLShipper on SLShipper.custid=C.custid GROUP BY C.CustID ), customerShipToRank as ( SELECT rtrim(SOA.CustID) AS 'custid' ,rtrim(SOA.ShiptoID) as 'shiptoid' ,COUNT(SLShipper.ShipperID) as 'totalShippers' ,cast(Row_number() OVER(Partition by SOA.custid ORDER BY COUNT(SLShipper.ShipperID) DESC) as int) AS 'ShipToRank' ,customerRank FROM [MLSQL12].[SLapplication15].dbo.soaddress SOA left outer join [MLSQL12].[SLapplication15].dbo.SOShipHeader SLShipper on SLShipper.CustID=SOA.CustId and SLShipper.ShiptoID=SOA.shiptoid join customerRank CR on CR.custid=SOA.CustID GROUP BY SOA.CustID ,SOA.ShiptoID ,customerRank ), combinedData as ( select COM.Docentry ,CXR.* ,CSTR.* from customerOrderMatches COM join MLSQL15.HistoricalData.Hist.CustomerXRef CXR on CXR.OrigShipToID=COM.OrigShipToID collate SQL_Latin1_General_CP850_CI_AS and CXR.OrigCustID=COM.OrigCustID collate SQL_Latin1_General_CP850_CI_AS left outer join customerShipToRank CSTR on CSTR.shiptoid =CXR.BKShiptoId and CSTR.custid =CXR.BKCustId ) select * from combinedData CD where CONCAT(customerRank,ShipToRank) in ( select MIN(CONCAT(customerRank,ShipToRank)) from combinedData group by docentry) order by docentry
补充信息
- 已知查询存在可优化的低效点,但这不应该导致结果不一致。
- 涉及的数据库包括SAP数据库、Microsoft Dynamics SL数据库,以及自研的并购数据专用数据库。
更新(2022年12月12日)
查询返回的其中一列是订单表的主键DocEntry,6次执行结果如下:
| 查询执行次数 | 总行数 | 最小DocEntry | 最大DocEntry |
|---|---|---|---|
| 1 | 14509 | 9 | 31412 |
| 2 | 14509 | 9 | 31412 |
| 3 | 5455 | 105 | 31408 |
| 4 | 5448 | 108 | 31411 |
| 5 | 14509 | 9 | 31412 |
| 6 | 5181 | 105 | 31411 |
1. ROW_NUMBER()排序键重复导致的不确定性
在customerRank和customerShipToRank两个CTE中,ROW_NUMBER()仅按COUNT(SLShipper.ShipperID)排序。如果多个客户(或客户收货地址)的totalShippers数值相同,SQL Server无法确定这些行的排序顺序,每次执行可能生成不同的customerRank或ShipToRank值。这种不确定性会传递到后续逻辑,导致最终筛选结果波动。
2. 字符串拼接的最小值比较逻辑缺陷
最终筛选使用CONCAT(customerRank,ShipToRank)取最小值,这种字符串比较存在逻辑问题:
- 例如
customerRank=10+ShipToRank=2拼接为102,customerRank=2+ShipToRank=10拼接为210,字符串比较会认为102更小,但这可能不符合实际业务的数值组合逻辑。 - 结合
ROW_NUMBER()的不稳定排序,拼接后的最小值会随机变化,直接导致筛选结果不一致。
3. 跨服务器查询的执行计划波动
跨服务器查询时,SQL Server可能选择不同的执行计划(比如数据拉取顺序、连接方式变化),如果执行计划影响到ROW_NUMBER()的计算顺序,会进一步加剧结果的不一致性。
修复方案
方案1:为ROW_NUMBER()添加稳定排序键
在ROW_NUMBER()的ORDER BY子句中加入唯一标识列,确保排序结果固定:
- 修改
customerRank中的排序逻辑:Row_number() OVER (ORDER BY COUNT(SLShipper.ShipperID) DESC, C.CustID) AS 'customerRank' - 修改
customerShipToRank中的排序逻辑:cast(Row_number() OVER(Partition by SOA.custid ORDER BY COUNT(SLShipper.ShipperID) DESC, SOA.ShiptoID) as int) AS 'ShipToRank'
添加唯一列(如CustID、ShiptoID)后,即使totalShippers相同,排序顺序也不会随机变化,ROW_NUMBER()生成的排名值将稳定一致。
方案2:修正最小值比较逻辑
避免字符串拼接,改用数值组合判断最小值,例如通过customerRank * 10000 + ShipToRank(假设ShipToRank不超过4位数)生成数值型组合键:
select * from combinedData CD where customerRank * 10000 + ShipToRank in ( select MIN(customerRank * 10000 + ShipToRank) from combinedData group by docentry) order by docentry
这种方式能避免字符串比较的逻辑错误,结合方案1的稳定排序,可彻底解决结果不一致问题。
方案3:强制固定执行计划(可选)
如果跨服务器执行计划波动是诱因,可使用查询提示(如OPTION(FORCE ORDER))强制SQL Server使用固定连接顺序,但需先分析执行计划后谨慎使用,避免引入性能问题。
内容的提问来源于stack exchange,提问作者Brendan Lesinski

