PAYATHON 2026

SQL Server CE Merge replication:0x80004005与0x80040e37

排障赵师傅

结论

Hr=80004005 是 Web Synchronization 返回的通用失败码,仅凭这个代码无法判断根因。0x80040E37 对应 DB_E_NOTABLE,表示同步时引用的表或视图不存在。这里的“表”可能是 SQL Server CE 中的业务表,也可能是发布端对象、筛选视图、触发器依赖对象、复制生成的对象,或初始化快照中的目标对象。

从现象看,问题更可能出在发布端的 Merge replication 元数据、快照或对象依赖关系上:

  • 已初始化的两台设备仍能同步。
  • 新建的空 .sdf 在初始化 bidirectional publication 时失败。
  • download-only publication 始终正常。
  • 删除并重建 publication 后曾短暂恢复。
  • SSMS 和自定义程序得到相同结果。

因此,设备程序可以暂时放在次要排查位置。现有设备能够同步也不能证明 publication 完整,因为这些设备通常不会重新执行快照中的建表和初始化脚本。

先保留故障现场,找到返回 DB_E_NOTABLE 的那条 SQL,再判断是否需要重建 publication。ACK:Error for DistributorSessionID = 1752 本身无法定位具体对象。

保留可复现的故障现场

先不要删除全部 publication。选择一个失败的 bidirectional publication,换用新的订阅者名称和新的 .sdf 文件,再次执行初始化。需要记录:

  • 精确到秒的开始时间和失败时间。
  • publication、article 和 subscriber 名称。
  • IIS 日志中的 DistributorSessionID。
  • SQL CE 异常集合中的所有错误,而非只有最外层 Message。
  • 同一时间段内 Snapshot Agent 和 Merge Agent 的详细输出。

已有设备能继续同步,而新设备无法初始化,这个差异有助于缩小排查范围。重建 publication 会删除相关元数据和快照,使故障现场无法继续分析。

输出完整的 SQL CE 错误集合

SqlCeException.Message 经常只显示最后一层错误。应遍历 Errors 集合,输出 HResult、NativeError、Source 和所有数值参数。

using System;
using System.Data.SqlServerCe;

public static class ReplicationTest
{
    public static void Synchronize()
    {
        var replication = new SqlCeReplication
        {
            InternetUrl =
                "https://iis-server.example.com/sqlce/sqlcesa35.dll",
            InternetLogin = "web-sync-user",
            InternetPassword = "password",

            Publisher = "SQLSERVER",
            PublisherDatabase = "ApplicationDatabase",
            Publication = "BidirectionalPublication",
            PublisherSecurityMode = SecurityType.NTAuthentication,

            Subscriber = "DiagnosticSubscriber03",
            SubscriberConnectionString =
                @"Data Source=C:\Temp\diagnostic.sdf;Password=password"
        };

        try
        {
            replication.AddSubscription(AddOption.CreateDatabase);
            replication.Synchronize();
        }
        catch (SqlCeException ex)
        {
            Console.WriteLine(ex.ToString());

            foreach (SqlCeError error in ex.Errors)
            {
                Console.WriteLine(
                    "HResult=0x{0:X8}; NativeError={1}; Source={2}; Message={3}",
                    error.HResult,
                    error.NativeError,
                    error.Source,
                    error.Message);

                if (error.NumericErrorParameters != null)
                {
                    for (int i = 0;
                         i < error.NumericErrorParameters.Length;
                         i++)
                    {
                        Console.WriteLine(
                            "NumericErrorParameters[{0}]={1}",
                            i,
                            error.NumericErrorParameters[i]);
                    }
                }
            }
        }
        finally
        {
            replication.Dispose();
        }
    }
}

请将示例中的地址、身份验证方式和密码替换为实际配置。正式日志中不要记录密码或完整的连接凭据。

如果订阅已经存在,不要再次调用 AddSubscription(AddOption.CreateDatabase)。可以根据测试目标使用新的数据库,或对已有订阅直接调用 Synchronize()。

获取服务器端实际失败的 SQL

IIS 文件中的以下内容只是会话失败通知:

Hr=80004005 ACK:Error for DistributorSessionID = 1752

DistributorSessionID 只能用于关联日志,不能说明根因。检查同一目录中、同一时间生成的其他日志和 SQL 文件,并保留原始文件。查找以下内容:

  • SQL Server 错误号,例如 208。
  • Invalid object name。
  • 完整的 CREATE TABLE、ALTER TABLE、INSERT 或存储过程调用。
  • 出错对象的两段式名称,例如 dbo.TableName。
  • 执行失败的触发器、视图或筛选过程。

还可以在 SQL Server Profiler 中执行一次范围尽量小的跟踪,关注:

  • Errors and Warnings / Exception
  • Errors and Warnings / User Error Message
  • TSQL / SQL:BatchCompleted
  • Stored Procedures / RPC:Completed

用数据库名、登录名、应用程序名和测试时间进行过滤。生产环境中不要长时间捕获全部 SQL,也不要无条件记录大字段。只需取得第一次失败时的服务器错误及其对应语句。

Replication Monitor 显示同步“已启动”,只能证明请求进入了复制流程,不能证明快照已完整应用。

核对 publication 和 article 元数据

对每个失败的 publication 执行以下存储过程,并保存结果:

EXEC sys.sp_helpmergepublication
    @publication = N'BidirectionalPublication';

EXEC sys.sp_helpmergearticle
    @publication = N'BidirectionalPublication';

EXEC sys.sp_helpmergefilter
    @publication = N'BidirectionalPublication';

逐项核对:

  • article 指向的源对象是否仍然存在。
  • source_owner、destination_owner 和对象名是否正确。
  • 表改名、迁移 schema 或恢复数据库后,article 是否仍在沿用旧配置。
  • row filter、join filter 引用的表和列是否存在。
  • 初始化脚本、creation_script 或自定义同步对象是否仍引用旧表名。
  • bidirectional article 的 upload_options、冲突解析和权限设置是否符合预期。
  • 复制所需的 ROWGUIDCOL、主键和 identity range 配置是否完整。

可用以下查询确认三个源表的 schema:

SELECT
    s.name AS schema_name,
    t.name AS table_name,
    t.object_id
FROM sys.tables AS t
JOIN sys.schemas AS s
    ON s.schema_id = t.schema_id
WHERE t.name IN
(
    N'BidirectionalTable1',
    N'BidirectionalTable2',
    N'BidirectionalTable3'
);

不要只核对表名。dbo.Sample 与其他 schema 下的 Sample 是不同对象。数据库迁移、更改所有者或从备份恢复后,article 元数据可能仍保留旧的对象关系。

检查触发器和其他依赖对象

Bidirectional article 会经过上传和服务器端 DML 路径,download-only article 则不一定会触发这些代码。可以先检查三个双向表上的用户触发器、约束、视图和存储过程。

SELECT
    OBJECT_SCHEMA_NAME(tr.parent_id) AS table_schema,
    OBJECT_NAME(tr.parent_id) AS table_name,
    SCHEMA_NAME(tr.schema_id) AS trigger_schema,
    tr.name AS trigger_name,
    tr.is_disabled,
    sm.definition
FROM sys.triggers AS tr
JOIN sys.sql_modules AS sm
    ON sm.object_id = tr.object_id
WHERE tr.parent_id IN
(
    OBJECT_ID(N'dbo.BidirectionalTable1'),
    OBJECT_ID(N'dbo.BidirectionalTable2'),
    OBJECT_ID(N'dbo.BidirectionalTable3')
);

然后检查能够静态解析的未绑定依赖:

SELECT
    OBJECT_SCHEMA_NAME(d.referencing_id) AS referencing_schema,
    OBJECT_NAME(d.referencing_id) AS referencing_object,
    d.referenced_database_name,
    d.referenced_schema_name,
    d.referenced_entity_name
FROM sys.sql_expression_dependencies AS d
WHERE d.referenced_id IS NULL
  AND d.referenced_entity_name IS NOT NULL
ORDER BY
    referencing_schema,
    referencing_object;

查询结果中可能包含跨数据库引用或其他合法的延迟解析对象,需要逐项核实,不能把每一行都视为故障。动态 SQL 中的对象名通常不会完整出现在依赖视图中,因此还要搜索模块定义和触发器代码。

验证初始化快照

既有订阅能够继续同步,而新订阅初始化失败时,应重点检查初始化快照:

  1. 在 Replication Monitor 中查看对应 Snapshot Agent 的最后一次运行记录。
  2. 确认快照生成时间晚于最近一次表结构、筛选条件或 article 属性变更。
  3. 检查快照目录中该 publication 的 .sch、.bcp 和相关脚本是否齐全。
  4. 核对 Snapshot Agent、SQL Server Agent 和 IIS 应用程序池身份是否有权读取快照共享目录。
  5. 确认磁盘空间充足,快照文件未被清理程序、防病毒软件或人工部署删除。
  6. 重新生成受影响 publication 的快照,再用一个全新的 subscriber 验证。

如果重新生成快照后恢复正常,仍需检查 Snapshot Agent 的详细日志。后续处理取决于具体原因,需要分清旧快照是否包含过期对象名,以及快照文件是否不完整或无法读取。

检查组件版本和实际加载的 DLL

记录 SQL Server CE 3.5 SP2、桌面测试工具、设备程序和 IIS Server Tools 的完整文件版本。安装列表不能准确反映程序实际使用的 CU,因为程序可能从应用目录加载另一份 System.Data.SqlServerCe.dll。

建议记录:

  • 设备上 System.Data.SqlServerCe.dll 的文件版本。
  • WinForms 工具实际加载的程序集路径及版本。
  • IIS 上 SQL Server CE Server Tools 的文件版本。
  • IIS 应用程序池位数和所用 ISAPI 路径。
  • SQL Server 的精确 build。

如果所有新订阅都在同一个服务器步骤失败,而旧订阅正常,版本差异通常不是排查重点。不过,仍需排除混装 CU、位数不匹配,以及应用目录中的旧 DLL 被优先加载等问题。

修复顺序

取得失败 SQL 和对象名后,按对象范围处理:

  1. 修复缺失、已改名或 schema 不一致的表、视图及触发器依赖。
  2. 修正 article、filter 或初始化脚本中的旧对象名。
  3. 为受影响的 publication 重新生成快照。
  4. 使用新的 subscriber 和新的 .sdf 完成初始化及双向上传测试。
  5. 验证成功后,再逐台重新初始化设备。
  6. 确认复制元数据已无法修复后,才删除并重建 publication。

不要同时删除全部 publication、subscription 和设备数据库。这种做法或许能恢复服务,却无法确认具体是哪项操作消除了故障,也无法避免相同的对象依赖或部署流程再次破坏快照。

现有日志不足以确定唯一根因。排查时需要拿到 0x80040E37 出现时执行的完整 SQL,以及该语句实际引用的对象。有了这两项信息,才能判断问题是缺失对象、schema 错误、依赖失效、快照陈旧,还是复制元数据不一致。

备注:内容仅供参考。