navicat 同步 sql server 必然触发锁等待,因其默认以 read committed 隔离级别在事务中执行带共享锁的全表扫描,不支持 nolock 或快照读,且无法配置隔离级别;同步过程易被未提交事务、触发器或元数据锁阻塞。
navicat 同步 sql server 时出现锁定超时(如 lock wait timeout exceeded 或错误 1222),根本原因不是“同步太慢”,而是它默认用事务 + 共享锁读取全表,且无法绕过——sql server 在同步过程中对源表加 s 锁,若目标库有长事务、未提交操作或阻塞会话,就会卡住。
为什么 Navicat 同步 SQL Server 必然触发锁等待
Navicat 的「数据同步」功能在 SQL Server 上不走快照读(snapshot isolation),也不支持 NOLOCK 提示;它内部封装的 SELECT 语句隐式运行在 READ COMMITTED 隔离级别下,且全程包裹在事务中。这意味着:
- 只要同步涉及无索引 WHERE 条件或大表,就会触发全表扫描并持有表级/页级
S锁 - 目标库若有未提交事务(哪怕只是开了个 SSMS 窗口没关),就会阻塞 Navicat 的
INSERT/UPDATE步骤 - Navicat 不暴露任何隔离级别或查询提示配置项,
WITH (NOLOCK)手动加在界面 SQL 框里无效 - 错误日志里看到
wait_duration_ms超过 5000 毫秒,基本可确认是sys.dm_os_waiting_tasks中的LCK_M_S或LCK_M_U类型等待
快速定位当前谁在 hold 锁
别等 Navicat 报错后再查——在同步开始前,先连上 SQL Server 执行以下检查:
- 查阻塞链:
SELECT blocking_session_id, session_id, wait_type, wait_duration_ms FROM sys.dm_os_waiting_tasks WHERE blocking_session_id 0; - 查活跃事务及持续时间:
SELECT session_id, transaction_id, transaction_begin_time, DATEDIFF(s, transaction_begin_time, GETDATE()) AS duration_sec FROM sys.dm_tran_active_transactions t JOIN sys.dm_tran_session_transactions st ON t.transaction_id = st.transaction_id WHERE DATEDIFF(s, transaction_begin_time, GETDATE()) > 30; - 查具体锁资源:
SELECT request_session_id, resource_type, resource_description, request_mode, request_status FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID('your_db_name');
重点看 blocking_session_id 非 0 的行,以及 request_status = 'WAIT' 的记录。常见元凶是 SSMS 里开着未提交的 BEGIN TRAN,或应用端连接池泄漏。
绕过 Navicat 同步锁的实操替代方案
真正安全的同步,必须放弃图形化一键同步,改用可控的分批脚本。核心原则:不启长事务、不扫全表、不依赖 Navicat 内部逻辑。
- 用
bcp导出 +BULK INSERT导入:导出时加-h "TABLOCK"提升性能,导入时指定WITH (TABLOCK, ROWS_PER_BATCH = 10000),避免单批次锁太久 - 若需条件同步,改写为按主键范围分片:
SELECT * FROM t1 WHERE id BETWEEN 10000 AND 19999,每次同步 1 万行,用 Navicat 执行多段 SQL 替代“同步向导” - 启用数据库级快照隔离(非会话级):
ALTER DATABASE your_db_name SET ALLOW_SNAPSHOT_ISOLATION ON;,再配合SET TRANSACTION ISOLATION LEVEL SNAPSHOT手动执行同步脚本(注意:Navicat 同步向导仍不认这个,必须自己写) - 绝对禁止在生产窗口期运行同步——哪怕只读,也会因 MDL(metadata lock)阻塞
ALTER TABLE或CREATE INDEX
最容易被忽略的陷阱
很多人修复了阻塞会话,却仍遇到超时,问题往往藏在更底层:
- SQL Server 实例开启了
READ_COMMITTED_SNAPSHOT,但 Navicat 同步仍走传统锁路径——因为该选项只影响READ COMMITTED下的读行为,而 Navicat 同步的写操作(INSERT/UPDATE)仍需获取排他锁 - 目标表有触发器(尤其是 AFTER 触发器),会延长事务生命周期,放大锁持有时间
- Navicat 连接字符串里没显式指定
ApplicationIntent=ReadOnly(仅适用于只读同步场景),导致 SQL Server 无法路由到只读副本(如果用了 AlwaysOn) - Windows 事件日志里
MSSQLSERVER_845错误看似无关,实则说明用户映射缺失——此时 Navicat 会降级为低效的权限校验流程,间接拉长连接建立时间,加剧超时概率











