A table change script can finish in seconds on a small copy and block production for a long time. Review the operation's data movement and lock requirements before booking the change window.

Classify What the Table Change Script Does
Adding a nullable column without a default is generally a metadata change. Changing a data type, narrowing a column, or adding a computed persisted value can require touching many rows. Some NOT NULL additions with eligible constant defaults use metadata optimizations in supported editions and versions; do not assume every default qualifies. I check the exact DDL form and table features before predicting the work. A large table with compression, temporal history, replication, or dependencies deserves a rehearsal that matches those features.
Even a metadata-only ALTER TABLE needs a schema modification lock. It can wait behind a long-running query, then block new work queued behind it. That queue is why a command that runs quickly in isolation can cause a visible outage.
Inspect the Table and Its Dependents
Count rows and pages from metadata, inspect indexes, constraints, triggers, computed columns, views, and foreign keys. Note whether the target is partitioned or participates in CDC or temporal tables. I also check the longest active requests and transactions near the planned window. The DDL script should name the target schema explicitly. An unqualified table name adds an unnecessary guessing game to an operation that already has enough risk.
SELECT s.name AS schema_name, t.name AS table_name,
SUM(ps.row_count) AS estimated_rows,
SUM(ps.reserved_page_count) AS reserved_pages
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
JOIN sys.dm_db_partition_stats AS ps ON ps.object_id = t.object_id
WHERE ps.index_id IN (0,1)
GROUP BY s.name, t.name
ORDER BY reserved_pages DESC;Rehearse the Table Change Script on Realistic Data
Run the exact script on a recent restored copy. Observe duration, log growth, blocking, and the resulting schema. A small development table cannot tell you whether an operation rewrites every row. I keep the same indexes and options in the rehearsal. If the test copy is much smaller, report that limit instead of presenting its duration as a production forecast. Watch tempdb and file free space when an online operation builds a replacement structure. The word ONLINE does not mean no resources or no blocking.
Ask what happens if the operation reaches the end of the window unfinished. Some DDL can be rolled back, but rollback itself takes time and log space. Build the stop decision into the plan rather than making it under pressure.
Use Online and Low-Priority Options Where Supported
Some online index and table operations support WAIT_AT_LOW_PRIORITY so their final schema lock does not sit at the front of a busy queue indefinitely. The syntax is tied to specific operations, not a general decoration for every ALTER TABLE statement. Check the documented options for your exact change and SQL Server edition. Choose MAX_DURATION and ABORT_AFTER_WAIT behavior deliberately. Killing blockers is a major operational decision; choosing SELF lets the waiting operation give up. I do not add ONLINE = ON by habit without testing the full statement.
For changes with no supported online path, use an expand-and-contract approach. Add a new nullable column, backfill in batches, and update application writes and reads. Then enforce the final constraint in a later step. That spreads work and gives you checkpoints.

Batch the Backfill
Use a stable key range, commit each batch, and log progress. Keep transactions short enough for log backups and ordinary work to advance. A TOP update without deterministic key selection can revisit rows or starve a hot section. Throttle between batches if the system needs room. Validate the count of remaining NULL or old-format values after every batch, and make reruns safe. The sample walks the primary key in fixed ranges and prints its progress. Adapt the key and target predicate to your schema.
CREATE TABLE #Backfill (id int NOT NULL PRIMARY KEY, new_value int NULL);
INSERT #Backfill(id) VALUES (1),(2),(3);
DECLARE @from int = 0, @batch int = 2;
DECLARE @max int = (SELECT MAX(id) FROM #Backfill);
WHILE @from < @max
BEGIN
UPDATE #Backfill SET new_value = id
WHERE id > @from AND id <= @from + @batch AND new_value IS NULL;
SET @from += @batch;
PRINT CONCAT(N'Done through id ', @from);
END;
SELECT COUNT(*) AS remaining_rows FROM #Backfill WHERE new_value IS NULL;Pair the Table Change Script With a Rollback
Save the current definition and the script to reverse each stage. A nullable column added and not yet read by the application is simple to abandon. A column populated and then used for writes needs a data reconciliation plan. A dropped column cannot be recovered by recreating its name. I record the point after which rollback requires restore or a forward fix. Test the reversal on the restored copy, including permissions and dependencies. Keep the operator's steps short and ordered.
Do not leave a transaction open across a long backfill to make it "safe." That usually makes locks and log growth worse. Safety comes from idempotent batches, validation, and a clear stop point.
Check the Lock Queue Before the Window
Run a read-only blocking check shortly before starting the DDL. Long transactions, open report cursors, and unattended sessions can hold schema stability locks that delay a schema modification lock. Once the DDL waits, later queries can line up behind it. I set a short lock timeout for a metadata change that must not disrupt the day, then reschedule if it cannot acquire the lock. The exact timeout belongs to the change plan. A command that gives up cleanly is better than a command that waits indefinitely while the application queue grows.
Capture the blocker and its owner before taking action. Do not kill a long session simply to make the deployment green. If WAIT_AT_LOW_PRIORITY is supported for the chosen online operation, choose the self-abort path or blocker policy deliberately and rehearse it. For ordinary ALTER TABLE forms without that option, a staged backfill or a quiet maintenance slot is the safer design. The locking strategy should match the actual syntax, not a generic checklist item.
What will the operator do if the script is waiting for a schema lock at the end of the window? Write the stop condition and rollback step into the runbook before deployment. I test that decision on a restored copy and check that the monitoring query identifies the blocker. The question should be answered while the team is calm, not while application requests queue behind the DDL.
Watch the Window and Verify the Result
At execution time, capture who is blocking whom, log space, and step progress. Stop if the agreed threshold is reached. After the DDL, verify column type, nullability, defaults, constraints, index status, and application reads and writes. Run the backfill validation query and check for a plan regression on key statements. I keep a short observation period after the change before declaring it done. A green deployment message only proves the script finished, not that the workload is healthy.
The best table change script includes the SQL and the operating plan around it. Production does not care whether the test script looked elegant. It cares whether the lock, data movement, and recovery path were understood.
Related reading on this blog: Ins and Outs of Online Index Operations and Altering Column: From NULL to NOT NULL.

A schema change is not one statement, it is a lock and data movement plan.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.





1 Comment. Leave new
Hi Pinal,
I have one question regarding bcp utility. I have a server on remote area. It has SQL Server 2005 installed. I want to import data into that using bcp utility. But I have SQL Server 2000 client on my machine. How can I import data into that server? Please let me know as I am using first time that bcp utility. Do I need SQL Server 2005 client on my machine for this? How can I code this?
Thanks in Advance
Jitesh