Introduction
SQL Server keeps a detailed record of every change in the transaction log. Most of the time, you work with this indirectly—through backups, replication, or recovery. But there’s a hidden, undocumented function called fn_dblog() that allows you to query the active transaction log directly.
This function can help you:
- Investigate deletes and updates.
- Trace DDL and DML activity.
- Troubleshoot issues like accidental data loss.
- Analyze replication and recovery behavior.
Since it’s undocumented, it’s not officially supported by Microsoft and could change without warning. Still, for DBAs and forensic analysts, it’s a powerful tool.
How fn_dblog() Works
fn_dblog() exposes the contents of the active portion of the transaction log. The basic syntax looks like this:
SELECT *
FROM fn_dblog(NULL, NULL);
- The two
NULLparameters tell SQL Server to return everything currently in the active log. - You can also pass LSNs (Log Sequence Numbers) to limit the range, but
NULLis usually fine for investigation.
👉 Important:fn_dblog() works only on the active log. If the log has been backed up and truncated, you won’t see older transactions.
Key Columns in the Output
The output of fn_dblog() has dozens of columns (around 130+ depending on version), but a few are especially useful:
| Column | Description |
|---|---|
Operation | The type of log operation (e.g., LOP_INSERT_ROWS, LOP_DELETE_ROWS, LOP_BEGIN_XACT) |
Transaction ID | The transaction identifier. You can group by this to see the entire transaction. |
Context | The context of the operation (e.g., LCX_HEAP, LCX_CLUSTERED). |
AllocUnitName | Shows the table or index involved. |
Transaction Name | May include system operations like CREATE INDEX, DROPOBJ, or user transactions. |
Begin Time / End Time | Timestamps of when the transaction started and ended. |
SPID | The session that generated the transaction. |
Example query to narrow down on a specific table:
SELECT [Current LSN], Operation, Context, Transaction_ID, AllocUnitName, [Transaction Name]
FROM fn_dblog(NULL, NULL)
WHERE AllocUnitName LIKE '%YourTableName%';
Finding Deleted Data
If you’re investigating accidental deletes, fn_dblog() can help identify who deleted what and when.
SELECT [Current LSN], Operation, Context, AllocUnitName, [Transaction Name], [Begin Time], [End Time], [SPID]
FROM fn_dblog(NULL, NULL)
WHERE Operation = 'LOP_DELETE_ROWS';
While fn_dblog() doesn’t directly return the deleted row’s data, it can give you:
- Transaction details
- Table involved
- When the delete happened
- Who did it (SPID)
This can help you combine it with point-in-time restores or third-party tools to recover data.
Investigating DDL and DML Activity
You can also catch:
- Table drops (
DROPOBJ) - Index rebuilds
- Bulk inserts
- Updates
Example:
SELECT [Current LSN], Operation, Transaction_ID, AllocUnitName, [Transaction Name]
FROM fn_dblog(NULL, NULL)
WHERE Operation IN ('LOP_DELETE_ROWS', 'LOP_INSERT_ROWS', 'LOP_MODIFY_ROW', 'LOP_DROP_OBJECT');
This gives you a quick forensic view of what’s happening in your database at the log level.
Filtering by Transaction
If you find a suspicious transaction, grab the Transaction_ID and filter down:
SELECT *
FROM fn_dblog(NULL, NULL)
WHERE Transaction_ID = '0000:00001234';
This shows every log record in that transaction, letting you reconstruct its flow step by step.
Caution: Undocumented Means Unsupported
- Microsoft doesn’t document or support
fn_dblog(). - Behavior may change between SQL Server versions.
- It requires
sysadminprivileges to run. - It only works on online databases, not backups or detached logs (for that, use
fn_dump_dblog()).
Advanced Example: Track Deletes in Real Time
You can wrap fn_dblog() in a job or monitoring procedure to detect deletes:
CREATE TABLE dbo.LogMonitor
(
LSN NVARCHAR(50),
Operation NVARCHAR(50),
AllocUnitName NVARCHAR(255),
TranName NVARCHAR(255),
BeginTime DATETIME,
EndTime DATETIME
);
INSERT INTO dbo.LogMonitor
SELECT [Current LSN], Operation, AllocUnitName, [Transaction Name], [Begin Time], [End Time]
FROM fn_dblog(NULL, NULL)
WHERE Operation = 'LOP_DELETE_ROWS';
You can then alert on this table using SQL Agent or custom monitoring.
Final Thoughts
fn_dblog() gives DBAs and security teams a window into the raw guts of SQL Server’s transaction processing. It can be a lifesaver when dealing with:
- Accidental data loss
- Forensics
- Replication troubleshooting
- Debugging unexpected behavior
But it also comes with risks:
- It’s unsupported and can break between versions.
- It requires elevated privileges.
- You can’t rely on it for long-term retention.
For production recovery, always pair it with proper transaction log backups and point-in-time restore strategies.
References
- Paul Randal – Inside the Transaction Log
- Microsoft Docs – Transaction Log Architecture
- SQL Server – fn_dump_dblog() deep dive
- MSSQLTips – Reading the Transaction Log
- Brent Ozar – Undocumented Functions
🧪 Workshop for Novice DBAs: Hands-On with fn_dblog()
This short lab will help you get familiar step-by-step:
- Create a test table
CREATE TABLE dbo.TestLog
(
ID INT IDENTITY(1,1),
DataValue NVARCHAR(100)
);
2. Insert some sample data
INSERT INTO dbo.TestLog (DataValue) VALUES ('First'), ('Second'), ('Third');
3. Delete one row
DELETE FROM dbo.TestLog WHERE ID = 2;
4. Run fn_dblog()
SELECT [Current LSN], Operation, AllocUnitName, [Transaction Name], [Begin Time], [End Time]
FROM fn_dblog(NULL, NULL)
WHERE AllocUnitName LIKE '%TestLog%';
✅ You’ll see LOP_DELETE_ROWS for the deleted record and LOP_INSERT_ROWS for the inserts.
- Identify the Transaction ID and rerun the query filtered by it to see all log records for that transaction.
Advanced Workshop Challenge
- Perform an
UPDATEinstead ofDELETEand locateLOP_MODIFY_ROWin the log. - Drop the table and check for
LOP_DROP_OBJECT. - Try wrapping this in a stored procedure to log all deletes to a monitoring table.
Discover more from SQLyard
Subscribe to get the latest posts sent to your email.


