SQL Server 2025 lab documentation
Step-by-step guide for the two scripts in this folder. Script 001 creates a demo database with a MEMORY_OPTIMIZED_DATA filegroup, a container, and a durable memory-optimized table. Script 002 drops In-Memory objects and removes the last container and filegroup so the XTP engine can stop. Full undeploy of the last memory-optimized container is supported starting in SQL Server 2025 (17.x).
In-Memory OLTP (also called Hekaton or XTP) stores selected tables in memory and persists them through a special
filegroup of type MEMORY_OPTIMIZED_DATA. That filegroup holds one or more containers
(folders on disk), not ordinary data files.
On SQL Server 2022 and earlier, after you dropped every memory-optimized object you still could not remove the last container and the memory-optimized filegroup. The In-Memory OLTP engine stayed loaded for that database.
Starting with SQL Server 2025 (17.x), you can remove every memory-optimized container and then
the filegroup when no In-Memory objects remain. When that completes,
sys.dm_db_xtp_undeploy_status.deployment_state is 0 and the XTP engine is stopped
for the database.
| File | Role |
|---|---|
001_create_db_FG_XTP_tables.sql |
Creates database InMemoryOLTP_Demo, adds the XTP filegroup and container, creates
dbo.CustomerMemory, loads sample rows, and reports XTP memory usage. |
002_remove_files_FG_XTP.sql |
Drops the memory-optimized table, takes backups, removes the last XTP container, then removes the filegroup. Includes status checks and extra log backups that help log truncation during undeploy. |
InMemoryOLTP_SQL2025_Lab_Guide.html |
This document. Open in a browser, or open in Microsoft Word and save as .docx if you need Word format. |
CHECKPOINT.model (usually FULL). Part B assumes you can take log backups so the log can truncate during undeploy.InMemoryOLTP_Demo. Microsoft does not support container removal while snapshots exist. Drop snapshots first.InMemoryOLTP_Demo if it already exists. Script 002 permanently drops
dbo.CustomerMemory. Adjust names and paths before using the pattern on any other database.
Not every machine has a D: drive. Choose the lab root from the number of local disks, then use that
same root in both SQL scripts (every FILENAME and TO DISK path).
| Windows disks | Lab root to use |
|---|---|
Only C: (typical laptop) |
C:\SQLLab\InMemoryOLTP |
C: and D: (or more data disks) |
D:\SQLLab\InMemoryOLTP — keeps lab files off the OS volume when a second drive exists |
The SQL scripts are written with C:\SQLLab\InMemoryOLTP. If the snippet below selects
D:, search-and-replace C:\SQLLab\InMemoryOLTP with
D:\SQLLab\InMemoryOLTP in both .sql files before you run them.
The SQL Server service account needs Modify permission on that folder tree.
Run this PowerShell on the SQL Server host. It picks D: when that drive exists, otherwise C:, creates the folders, and prints the root to copy into the scripts:
$labDrive = if (Test-Path -Path 'D:\') { 'D:' } else { 'C:' }
$labRoot = Join-Path $labDrive 'SQLLab\InMemoryOLTP'
New-Item -ItemType Directory -Force -Path @(
(Join-Path $labRoot 'Data'),
(Join-Path $labRoot 'Log'),
(Join-Path $labRoot 'XTP')
) | Out-Null
Write-Host "Lab root: $labRoot"
Write-Host "In both SQL scripts, every path must start with: $labRoot"
| Folder under the lab root | Used for |
|---|---|
Data\ |
Primary data file InMemoryOLTP_Demo.mdf |
Log\ |
Transaction log InMemoryOLTP_Demo.ldf |
XTP\ |
Parent folder. SQL Server creates the container folder XTPContainer here. |
| (lab root itself) | Backup files written by script 002 |
FILENAME = N'<lab-root>\XTP\XTPContainer'
(for example C:\SQLLab\InMemoryOLTP\XTP\XTPContainer or
D:\SQLLab\InMemoryOLTP\XTP\XTPContainer).
After creation you should see a directory at that path containing checkpoint and delta files, not a single data file.
Script: 001_create_db_FG_XTP_tables.sql
Run the entire script in one window connected to the SQL Server instance (context starts in master).
Recommended isolation: do not run this against a shared instance that already has a database named
InMemoryOLTP_Demo unless you intend to destroy it.
If InMemoryOLTP_Demo exists, the script sets it to SINGLE_USER with
ROLLBACK IMMEDIATE and drops it. That disconnects other sessions and discards the previous lab.
The database is created with a 256 MB primary file and 256 MB log, both growing by 128 MB. At this point there is no In-Memory filegroup yet.
ALTER DATABASE InMemoryOLTP_Demo ADD FILEGROUP InMemoryOLTP_Demo_XTP CONTAINS MEMORY_OPTIMIZED_DATA;
A database may have only one filegroup of this type. Adding it (and then a container) deploys the In-Memory OLTP engine for the database.
ALTER DATABASE InMemoryOLTP_Demo
ADD FILE
(
NAME = N'InMemoryOLTP_Demo_XTP_Container',
FILENAME = N'C:\SQLLab\InMemoryOLTP\XTP\XTPContainer' -- or D:\SQLLab\... if a D: drive exists
)
TO FILEGROUP InMemoryOLTP_Demo_XTP;
NAME is the logical name used later in ALTER DATABASE ... REMOVE FILE.
FILENAME is the container folder on disk.
The script queries sys.database_files joined to sys.filegroups. Confirm you see:
InMemoryOLTP_Demo_Data on PRIMARYInMemoryOLTP_Demo_LogInMemoryOLTP_Demo_XTP_Container on filegroup
InMemoryOLTP_Demo_XTP (type_desc is typically FILESTREAM for the container)Optional extra check after the filegroup exists:
USE InMemoryOLTP_Demo; SELECT deployment_state, deployment_state_desc FROM sys.dm_db_xtp_undeploy_status; -- Expect deployment_state 1 or 2 while XTP is deployed.
The script switches to InMemoryOLTP_Demo and creates:
dbo.CustomerMemory with MEMORY_OPTIMIZED = ONDURABILITY = SCHEMA_AND_DATA (schema and data survive restart; needs the XTP container)CustomerIDCustomerCodeMemory-optimized tables cannot use a clustered B-tree primary key. A hash or nonclustered (range) index is required.
CUST001 … CUST005).CUST000001 …). This is enough to show checkpoint files and memory stats; it is not a performance benchmark.
The closing query reads sys.dm_db_xtp_table_memory_stats (table + index memory in KB).
Non-zero values confirm the table is resident in the In-Memory engine.
dbo.CustomerMemory has about 10,005 rows;
memory stats are greater than zero; deployment_state is 1 or 2.
Script: 002_remove_files_FG_XTP.sql
Microsoft’s supported order is: confirm XTP is deployed → drop all In-Memory objects → remove all containers
(last container starts undeploy, which can be long-running) → remove the filegroup → confirm
deployment_state = 0.
dbo.CustomerMemory permanently deletes that table’s data. Script 002 takes a full backup
after the drop, which is useful for the database shell but does not restore the dropped table’s rows unless you
already had a backup from Part A. If you need the table data, take a full backup before running script 002.
BACKUP LOG while REMOVE FILE is running, as noted in the script comments.Connect both windows to the same instance. Window 1 starts in master.
The lab only created a table, so the script runs:
DROP TABLE IF EXISTS InMemoryOLTP_Demo.dbo.CustomerMemory;
On a real database you must also drop:
sys.tables.is_memory_optimized = 1)sys.all_sql_modules.uses_native_compilation = 1)sys.table_types.is_memory_optimized = 1)Use the inventory queries in Microsoft’s article (also copied in section 8) before you remove files.
Script 002 then:
<lab-root>\InMemoryOLTP_Demonew.BAK (WITH INIT overwrites that file).InMemoryOLTP_Demo_Undeploy.trn.CHECKPOINT.
Under FULL recovery, log backups are often required so start_of_log_lsn can move past
undeploy_lsn. That is why the script repeats log backups later.
The script queries InMemoryOLTP_Demo.sys.dm_db_xtp_undeploy_status. At this moment XTP is still
deployed (no container has been removed yet), so you typically still see state 1 or 2.
The comment about waiting until start_of_log_lsn > undeploy_lsn applies after last-container
removal has started (state 3), not before the first REMOVE FILE.
ALTER DATABASE InMemoryOLTP_Demo REMOVE FILE InMemoryOLTP_Demo_XTP_Container;
Removing the last container starts XTP undeploy. This statement can run for a long time.
Do not treat a long wait as a hang until you have checked deployment_state (section 7).
If you cancel or abort this statement, undeploy may be partial. Microsoft documents
deployment_state = 6 in that case: run REMOVE FILE again to finish.
While Window 1 is still waiting on REMOVE FILE, use Window 2 (as the script comments say):
BACKUP LOG InMemoryOLTP_Demo TO DISK = 'C:\SQLLab\InMemoryOLTP\InMemoryOLTP_Demo_Undeploy_02.trn' -- or D:\SQLLab\... WITH INIT, STATS = 10;
You may also run CHECKPOINT in the demo database from Window 2. Repeat log backup / checkpoint if
state stays at 3 (see section 7).
Only after the container is gone:
ALTER DATABASE InMemoryOLTP_Demo REMOVE FILEGROUP InMemoryOLTP_Demo_XTP;
If this fails, the container is still present or undeploy has not finished. Check files and deployment_state again.
The script switches to the database and queries undeploy status again. Success is:
deployment_state = 0sys.database_filesMEMORY_OPTIMIZED_DATA filegroup in sys.filegroups
A final log backup in the script (overwriting InMemoryOLTP_Demo_Undeploy.trn) is optional hygiene
after undeploy; it is not what stops the engine.
deployment_state is 0.
SQL Server is no longer running the In-Memory OLTP engine for this database.
sys.dm_db_xtp_undeploy_statusRun this in the user database (or three-part name from master):
SELECT
deployment_state,
deployment_state_desc,
undeploy_lsn,
start_of_log_lsn
FROM sys.dm_db_xtp_undeploy_status;
| deployment_state | Meaning (SQL Server 2025) | Typical action |
|---|---|---|
| 0 | XTP is not deployed, or undeploy finished and the engine is stopped. | Nothing further for undeploy. If you expected XTP to be on, the filegroup/container is missing. |
| 1 or 2 | In-Memory OLTP engine is deployed. | Safe to start the Microsoft removal sequence after objects are dropped. |
| 3 | Last-container removal in progress. Waiting on checkpoint and/or log truncation
(start_of_log_lsn must pass undeploy_lsn). |
See section 7.1. CHECKPOINT, log backup, and fix log_reuse_wait_desc if needed. |
| 4 | Active log has passed undeploy LSN; waiting for the final undeploy log record. With availability groups, the record must reach all replicas. | Usually completes in seconds. If it sticks, investigate replica apply lag. |
| 5 | Waiting for the final XTP checkpoint. | Run CHECKPOINT in the database to speed this up. |
| 6 | REMOVE FILE for the last container was canceled or aborted. |
Execute ALTER DATABASE ... REMOVE FILE again. |
Always prefer deployment_state_desc on your build if the numeric mapping above ever differs slightly;
the actions Microsoft documents for 3, 4, 5, and 6 are the ones this lab follows.
undeploy_lsn is 0, run CHECKPOINT; in the database.undeploy_lsn is not 0, the log must truncate until
start_of_log_lsn is greater than undeploy_lsn. Run CHECKPOINT, wait about
a minute, and repeat. Under FULL or BULK_LOGGED recovery, also back up the log (script 002 already does this).SELECT name, recovery_model_desc, log_reuse_wait_desc FROM sys.databases WHERE name = N'InMemoryOLTP_Demo';Common waits:
LOG_BACKUP (take another log backup), ACTIVE_TRANSACTION (commit or kill
the old transaction), AVAILABILITY_REPLICA, DATABASE_MIRRORING, and others documented
under “Factors that can delay log truncation”. If log_reuse_wait_desc is NOTHING but
state is still 3, Microsoft still recommends another transaction log backup; more than one may be required.CHECKPOINT;REMOVE FILE should complete and state becomes 0. Then remove the filegroup.USE InMemoryOLTP_Demo;
SELECT object_id,
OBJECT_SCHEMA_NAME(object_id) AS schema_name,
name AS table_name
FROM sys.tables
WHERE is_memory_optimized = 1;
SELECT object_id,
OBJECT_SCHEMA_NAME(object_id) AS schema_name,
OBJECT_NAME(object_id) AS module_name
FROM sys.all_sql_modules
WHERE uses_native_compilation = 1;
SELECT SCHEMA_NAME(schema_id) AS type_schema_name,
name AS type_name,
OBJECT_NAME(type_table_object_id) AS type_table_name
FROM sys.table_types
WHERE is_memory_optimized = 1;
USE InMemoryOLTP_Demo;
SELECT df.name, df.type_desc, df.physical_name, fg.name AS filegroup_name, fg.type_desc AS fg_type
FROM sys.database_files AS df
LEFT JOIN sys.filegroups AS fg
ON df.data_space_id = fg.data_space_id;
SELECT COUNT(*) AS customer_rows FROM InMemoryOLTP_Demo.dbo.CustomerMemory;
REMOVE FILE on XTP containers.REMOVE FILE.InMemoryOLTP_Demo.DROP DATABASE InMemoryOLTP_Demo; from master if you no longer need it. Delete leftover backup files under your lab root (C:\SQLLab\InMemoryOLTP or D:\SQLLab\InMemoryOLTP) and empty XTP folders if Windows still shows them after a successful REMOVE FILE.SELECT @@VERSION;D: root, replace C:\SQLLab\InMemoryOLTP with that root in both SQL scripts. Grant the service account access to the folders.001_create_db_FG_XTP_tables.sql, confirm every file path uses the same lab root, then execute.002_remove_files_FG_XTP.sql in window 1; when REMOVE FILE is running, execute the extra BACKUP LOG in window 2.REMOVE FILE does not return, poll dm_db_xtp_undeploy_status and follow section 7.sys.databases.log_reuse_wait_desc)
This guide documents the scripts as they exist in this folder and maps them to Microsoft’s SQL Server 2025 undeploy
procedure. Database and backup file names are lab-specific. Disk paths default to
C:\SQLLab\InMemoryOLTP; use D:\SQLLab\InMemoryOLTP when a D: drive exists.