Six SQL Server Log Reader Columns Missing After 2019

A transaction log query that worked before an upgrade can fail before it reads a single log row. On the 2025 instance, selecting [Repl CSN] from sys.fn_dblog returns Invalid column name 'Repl CSN'.

A woman DBA runs a pencil along six empty column outlines on a long printed ledger, with an older filled-in copy open beside her.

This post is part of the Undocumented Changes in SQL Server 2022 and 2025 series. The contents page lists every part in order.

I expect that kind of break from undocumented log readers. Knowing the changed rowset helps identify scripts that can fail after an upgrade. In the sys.fn_filelog post, I mentioned the first count. sys.fn_dblog and sys.fn_dump_dblog had 130 output columns on the 2019 instance, and 124 on the 2022 and 2025 instances. I went back to the inventory files and local tests to identify the six missing columns.

Paul Randal wrote about the undocumented fn_dblog function, and about fn_dump_dblog for reading log backup files, as tools for transaction log investigation. He describes fn_dblog reading log records still present in the active portion of the log. He also describes fn_dump_dblog reading log backup files without restoring the database.[1] Those functions are useful. Microsoft does not give us the compatibility promise we get for documented system views.

The six columns

The six columns were removed. They were not renamed. The 2019 inventory has these six columns on both sys.fn_dblog and sys.fn_dump_dblog. The 2022 and 2025 inventories do not have them on either function:

Column 2019 type Nullable
Repl Partition ID int yes
Repl Epoch int yes
Repl CSN bigint yes
Repl Flags int yes
Repl Msg varbinary(8000) yes
Repl Source Commit Time nvarchar(24) yes

The 124 shared columns kept the same names, data types, and nullability in the metadata I checked. I did not find a 2022-only replacement column. In other words, a script that selects [Repl CSN] cannot be repaired by changing the spelling to a new column name. The column is gone from the rowset on the tested builds.

The ordinal positions changed too. In 2019, the removed columns occupied positions 55 through 60. On the 2025 instance, the columns after that point moved six positions earlier. A script that reads by column name gets an invalid column name error. A tool that reads by ordinal position can read the wrong column after the removal point.

The definitions changed by one internal argument

The two wrapper definitions, which I read over the Dedicated Admin Connection (DAC), show a related change in both functions.

sys.fn_dblog still has the same public parameters in all three versions:

The definition body still calls the same internal rowset provider:

The public signature did not change. The 2022 and 2025 definitions pass one more NULL placeholder into the internal OpenRowset (TABLE DBLOG, ...) call than the 2019 definition does.

The sys.fn_dump_dblog definition has the same difference. Its public signature still has @start, @end, @devtype, @seqnum, and 64 file-name parameters. Its 2022 and 2025 definitions also have one more internal NULL placeholder before the file-name arguments than the 2019 definition has. I am not going to assign a meaning to that placeholder because the provider is undocumented. The evidence shows that the wrapper text changed, and the exposed rowset lost six replication-named columns.

The local test

I created the same scratch database on the 2019 and 2025 instances, then inserted, updated, and deleted one row. The script below creates a scratch database and writes to it, so run it only on a test instance. It is the version I used on the 2019 instance:

On the 2019 instance, the query returned rows and all six removed columns were NULL in the first ten rows. I also counted all rows returned by sys.fn_dblog(NULL, NULL) in that scratch database after the activity. The function returned 397 rows in my captured test run, and each of the six removed columns had zero non-NULL values in that test.

The local test does not prove the columns were always empty. Their names are replication-related, and I did not configure replication. The test only proves that these columns existed in the 2019 rowset and did not carry values for my small non-replication test.

On the 2025 instance, selecting one removed column failed before SQL Server read any log rows:

The error was:

A script that directly names one of these columns can fail with the same invalid-column error on a 124-column build.

What I found in community code

I searched for the removed column names with fn_dblog and fn_dump_dblog. I did not find a well-known maintained community tool in the fetched results that explicitly selects these six names. I did find public scripts that use the old 130-column shape.

One public GitHub script selects a full column list for the older fn_dblog rowset. The select list names all six removed columns. They are [Repl Partition ID], [Repl Epoch], [Repl CSN], [Repl Flags], [Repl Msg], and [Repl Source Commit Time].[2] The query shape can fail on the tested 2022 and 2025 builds with Msg 207.

Another public training script uses SELECT * from fn_dblog and fn_dump_dblog. The script then includes saved output headers with the old replication columns.[3] SELECT * itself does not fail. SQL Server returns the columns that exist. The risk is downstream: export parsers, documentation, or code that expects 130 columns can misalign when the rowset has 124.

I do not claim a named tool breaks unless I tested that tool. For a script, the rule is narrower and easier to test. A select list that names one of the six removed columns can fail on a build with the 124-column shape.

What to change in your scripts

If you have log-reader scripts, search for the six removed column names first. The search identifies scripts that can fail during query compilation.

Then check for ordinal access. Anything that reads column 60 as Repl Source Commit Time, or reads later columns by fixed position, needs a version-aware column map. On the tested builds, the shared columns after position 60 are still present, but their ordinal positions changed.

Finally, avoid SELECT * when you are feeding the result to an automated parser. SELECT * may be acceptable for manual inspection. It is unsafe when downstream code assumes 130 columns.

Scripts that name one of the six removed columns, or read later columns by position, need a check against the new rowset shape. Changing the column name cannot restore a removed column. If your script needs one of these columns, make it detect the rowset shape before it runs.

Have you seen one of these columns used in a log-reading tool I missed? Tell me on Bluesky or LinkedIn, and I will update the post.

References

  1. Using fn_dblog, fn_dump_dblog, and restoring with STOPBEFOREMARK to an LSN - SQLskills. Paul Randal describes fn_dblog and fn_dump_dblog as undocumented log-reading functions, with examples for active log records and log backup files. ↩
  2. Exemplo - Avancado - Relacao completa das colunas existentes na FN_DBLog.sql - GitHub raw file. Public script that selects a full column list for the older fn_dblog rowset, including the six columns absent from the tested 2022 and 2025 builds. ↩
  3. Sectie4.sql - GitHub raw file. Public training script that uses SELECT * from fn_dblog and fn_dump_dblog, with saved output headers that show the old 130-column shape. ↩