Fix it now
Error 20598 is published as the row not being found at the Subscriber when applying a replicated command for a named table, with the primary keys printed in the message. Transactional replication ships commands rather than rows, so a command that targets a missing row has nowhere to land. The fix is to find out how the copies drifted, not to skip errors forever.
USE distribution;
EXEC sp_browsereplcmds
@xact_seqno_start = '0x0000000000000000',
@xact_seqno_end = '0xFFFFFFFFFFFFFFFF';
- Open Replication Monitor, find the failing subscription and read the error detail. It gives you the transaction sequence number and the command id, and the 20598 message itself prints the table and the primary key values.
- Narrow the call above to that sequence number range and match the
command_idcolumn to the command id from the error. That gives you the exact statement the agent is trying to apply. - Query the Subscriber for that key and confirm the row really is absent, then query the Publisher for the same key so you know what should be there.
- Ask why it is absent before doing anything: a local purge job, a direct write, or an initialisation that did not match are the three usual answers.
- Reconcile properly, by validating the article and reinitialising just that article, rather than leaving the agent skipping errors indefinitely.
Microsoft’s documented quick remedy for a single known row is to insert it at the Subscriber so the Distribution Agent can retry the failed command. That is first aid: it does not tell you why the row went missing.
If the agent runs clean and validation agrees, stop here. If it fails again next week, the next section covers where the drift comes from.
Why it happens
The Log Reader Agent turns committed changes at the Publisher into commands, and the Distribution Agent replays them at the Subscriber. Nothing in that pipeline compares the two copies as it goes. An update or a delete is applied by key, so if the row with that key is not there, the command has nowhere to land and the agent stops. The message names the table and prints the primary keys, which is more help than most replication errors give you.
Drift comes from a small number of places. Somebody writes directly at the Subscriber, deliberately or through an application nobody remembered was pointed there. A retention or purge job runs locally and removes rows replication still expects. The subscription was initialised from a backup or by hand and the starting point did not match. Or errors were skipped in the past, so commands were dropped and every later command that depended on them now fails.
Error 2601 is the same problem seen from the other end. Its published text is that a duplicate key row cannot be inserted into the named object with the named unique index, and it prints the duplicate key value. An insert arrives for a key that already exists at the Subscriber and the unique index rejects it. When you see 20598 and 2601 alternating in one subscription, treat it as a single drift problem rather than two.
One more thing is worth knowing before you start changing data: the two copies can be compared properly. The tablediff utility is current, ships with the replication feature, and is documented as comparing rows between a source table at a Publisher and destination tables at Subscribers, with the option to generate a Transact-SQL script that brings the destination into convergence. It is a better starting point than reading rows by eye.
Something writes to the Subscriber
You have this one if The missing rows belong to one or two tables, and somebody has more than read rights on the subscription database.
- Find the writer: local jobs, ETL packages, and applications whose connection strings point at the Subscriber.
- Remove write permissions from anything that should be read-only, so the subscription database cannot drift again.
- Validate the affected articles, then reinitialise the ones that no longer match.
If an application genuinely has to write to those tables, replication is the wrong tool for them. Exclude them from the publication rather than fighting the same failure every week.
A local retention job deletes rows
You have this one if Failures appear on a schedule, always affecting older rows, and a purge or archive job runs at the Subscriber.
- Stop the local purge and let the deletes arrive through replication from the Publisher instead.
- If the Subscriber must hold less data than the Publisher, use a filtered article so the boundary is part of the publication.
- Reinitialise the affected article once the purge is disabled.
The subscription was initialised from a mismatched starting point
You have this one if Errors began immediately after the subscription was set up from a backup or with a manual copy of the data.
- Validate the publication at the Publisher:
EXEC sp_publication_validation @publication = N'YourPublication'; - For a small number of tables, reinitialise the individual articles rather than the whole subscription.
- For widespread differences, reinitialise the subscription and apply a fresh snapshot during a quiet period.
Errors have been skipped for a long time
You have this one if The agent runs with a profile that continues past data consistency errors, and nobody can say when the copies last matched.
- Check the agent profile and any parameter listing error numbers to be ignored, and record what it has been skipping.
- Compare the two copies with tablediff and keep the script it generates as evidence of how far apart they were.
- Plan a reinitialisation, then remove the skip setting so future drift is visible immediately.
Full reference
Reading the evidence
| What you find | What it points at |
|---|---|
| Failures in one table only | A local job or an application writing to that table at the Subscriber |
| Failures across many tables at once | The initialisation did not match, or errors were skipped earlier |
| 20598 and 2601 alternating | Rows are being deleted and reinserted locally at the Subscriber |
| Failures start after a maintenance window | A restore, a purge or a manual correction at the Subscriber |
| The key in the message is one you recognise | Go straight to whoever owns that data before touching replication |
Finding the command behind the error
- Take the transaction sequence number and command id from the agent’s error detail in Replication Monitor.
- Run
sp_browsereplcmdsat the Distributor, on the distribution database, narrowed to that sequence range. - Match the
command_idcolumn in the results to the command id from the error. - Read the
commandcolumn, which is the Transact-SQL the agent is trying to apply, and note thearticle_id. - Use that article id to identify the table at the Publisher and validate it.
Validation options, and what each costs
@rowcount_only |
What it does |
|---|---|
| 1 (default) | Row count comparison only. Cheap, and catches most drift |
| 0 | Checksum comparison compatible with older versions |
| 2 | Row count and binary checksum. The thorough option, and the slowest |
sp_publication_validation runs at the Publisher on the publication database and raises a validation request for every article in the publication. Run it before and after a reconciliation so you have a before-and-after rather than an impression.
tablediff, used properly
- It ships with the replication feature, and in SQL Server 2022 lives in the COM folder under the instance’s installation path.
- It can compare row counts and schema quickly, or go column by column.
- It can generate a Transact-SQL script that brings the destination into convergence, which is the output worth keeping.
- Read that script before you run it. It is a record of exactly how far apart the copies had drifted, and it is the argument for fixing the cause.
Telling the agent to continue past these errors does not repair anything. It discards the failed commands permanently, so the Subscriber falls further behind with every skip and the eventual reconciliation gets larger. Microsoft documents the skip parameter as a way to keep the rest of the changes flowing while you deal with the referential integrity question, not as a standing configuration.
Every code this article covers
| Code | What it points at | Source |
|---|---|---|
20598 |
The row was not found at the Subscriber when applying the replicated command for the named table, with the primary keys printed in the message | Microsoft Learn |
2601 |
Cannot insert a duplicate key row into the named object with the named unique index; the duplicate key value is printed | Microsoft Learn |
Confirm the fix worked
- Article or publication validation reports that row counts, and checksums if you used them, agree.
- The Distribution Agent runs to completion with no errors in its history.
- An insert, update and delete of a test row at the Publisher arrives at the Subscriber.
- No error-skipping parameter remains on the agent profile.
- Nothing other than replication holds write permission on the subscription database.
Questions people ask about this
Can I just insert the missing row by hand?
For a single known row it is a legitimate stopgap, and Microsoft documents it: insert the missing row at the Subscriber so the Distribution Agent can retry the failed command. It does not tell you why the row went missing, so treat it as first aid.
Does reinitialising mean copying the whole database again?
Not necessarily. You can reinitialise a single article, which drops and recreates just that table at the Subscriber from a new snapshot. That is far cheaper than a full snapshot of a large publication.
Is this a licensing or edition problem?
No, and it costs nothing to fix. It is a data consistency problem between two copies. Worth knowing separately: SQL Server Express can act as a subscriber but not as a publisher or distributor.
Is tablediff still available?
Yes. It is documented as current, ships with the replication feature, and can generate a Transact-SQL script to bring the destination into convergence rather than only reporting the differences.
How do I stop it happening again?
Keep the subscription database read-only for everything except replication, make sure no local job deletes or reshapes replicated tables, and validate articles on a schedule so drift is found while it is still small.
