A transaction is a unit of work. Either every required process-data change succeeds and commits, or the unit rolls back. Keep the unit small, deterministic and independent of operator waiting time or external network calls.
-
COMMITaccepts a valid unit;ROLLBACKundoes it. - Use
SET XACT_ABORT ONandTRY/CATCHfor reliable server-side error handling. - An inner
COMMITdoes not commit the outer transaction. - Design retries with an idempotency key because connection loss can hide a successful commit.
SQL TCL for SCADA and Industrial Automation
This guide covers SQL Server transactions, COMMIT and ROLLBACK, SAVE TRANSACTION, XACT_STATE and XACT_ABORT and a complete SCADA transaction lab.
1. What Is TCL?
TCL means Transaction Control Language. It controls the boundary and outcome of a transaction.
| Statement | Purpose | Automation example |
|---|---|---|
BEGIN TRANSACTION | Start an explicit unit of work | Begin event + state update |
COMMIT TRANSACTION | Accept a valid unit | Make both changes complete |
ROLLBACK TRANSACTION | Undo to transaction start/savepoint | Remove partial event write |
SAVE TRANSACTION | Create a rollback point | Undo an optional correction only |
SQL Server starts and ends transactions according to session settings and statements. Every application should know who owns the transaction: the server procedure, the client connection or a higher-level coordinator.
2. ACID Properties in Automation Data
| Property | Meaning | Industrial example |
|---|---|---|
| Atomicity | All or none of the unit succeeds | Event row and charge status move together |
| Consistency | Rules remain valid | Foreign keys and checks hold after commit |
| Isolation | Concurrent units have defined visibility | Reports do not consume half-finished changes |
| Durability | Committed changes survive failure according to database guarantees | A confirmed event remains recorded after restart |
A transaction cannot correct a bad process model. Constraints, keys, validation and idempotency still define what “consistent” means.
3. Autocommit, Implicit and Explicit Transactions
| Mode | Behavior | Recommendation |
|---|---|---|
| Autocommit | Each standalone statement is one transaction | Good for independent single statements |
| Implicit | Certain statements start a transaction; client must commit/rollback | Use only when client lifecycle is well controlled |
| Explicit | BEGIN TRANSACTION starts a named unit | Use for related statements that must succeed together |
SELECT @@OPTIONS AS [SessionOptions];
DBCC USEROPTIONS;
-- Explicitly control this setting for predictable scripts.
SET IMPLICIT_TRANSACTIONS OFF;4. BEGIN TRANSACTION and COMMIT
BEGIN TRANSACTION;
UPDATE [dbo].[tblEvent]
SET [Temp_Act] = 949.250
WHERE [ID] = 1250;
IF @@ROWCOUNT <> 1
BEGIN
ROLLBACK TRANSACTION;
THROW 50020, 'Expected one event row.', 1;
END;
COMMIT TRANSACTION;Start the transaction immediately before the first required statement. Validate while it is open, commit promptly and return a clear outcome to the caller.
5. ROLLBACK TRANSACTION
BEGIN TRANSACTION;
DELETE FROM [dbo].[tblEvent]
WHERE [ChargeNo] = 'CH-LAB-ROLLBACK';
SELECT @@ROWCOUNT AS [RowsDeletedInsideTransaction];
ROLLBACK TRANSACTION;
SELECT COUNT_BIG(*) AS [RowsAfterRollback]
FROM [dbo].[tblEvent]
WHERE [ChargeNo] = 'CH-LAB-ROLLBACK';A full rollback undoes data changes and releases transaction resources. Changes to local variables and table variables are not undone, so do not treat their content as proof that a database change committed.
6. Define the Automation Unit of Work
Group statements only when the business rule requires them to succeed together. This example records a transition and closes the previous event.
DECLARE @PreviousEventID int = 1249;
BEGIN TRANSACTION;
INSERT INTO [dbo].[tblEvent]
([DT], [SQF_No], [ChargeNo], [Event_From], [Event_To], [Temp_Set], [Temp_Act], [Fan_Status])
VALUES
(SYSUTCDATETIME(), 2, 'CH-2026-0050', 'Heating', 'Soaking', 950.000, 949.250, 1);
UPDATE [dbo].[tblEvent]
SET [Event_To] = 'Soaking'
WHERE [ID] = @PreviousEventID AND [SQF_No] = 2;
IF @@ROWCOUNT <> 1
BEGIN
ROLLBACK TRANSACTION;
THROW 50021, 'Previous event not found.', 1;
END;
COMMIT TRANSACTION;Do not include unrelated report queries, file writes, email calls or operator confirmations inside this database transaction.
7. TRY/CATCH with SET XACT_ABORT ON
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO [dbo].[tblEvent]
([DT], [SQF_No], [ChargeNo], [Event_From], [Event_To])
VALUES
(SYSUTCDATETIME(), 2, 'CH-2026-0051', 'Load', 'Heating');
UPDATE [dbo].[tblEvent]
SET [Fan_Status] = 1
WHERE [ID] = 1250;
IF @@ROWCOUNT <> 1
THROW 50022, 'Expected one target event.', 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH;XACT_ABORT ON causes many runtime errors to terminate and roll back a transaction. TRY/CATCH ensures your code inspects transaction state, cleans up and preserves the original error with THROW.
8. XACT_STATE and @@TRANCOUNT
| Check | Value | Meaning |
|---|---|---|
XACT_STATE() | 1 | Active and committable |
XACT_STATE() | 0 | No active transaction |
XACT_STATE() | -1 | Active but uncommittable; full rollback required |
@@TRANCOUNT | 0+ | Number of unmatched transaction begins for the session |
SELECT
XACT_STATE() AS [TransactionState],
@@TRANCOUNT AS [TransactionCount];Use XACT_STATE() to decide whether a transaction can commit. Use @@TRANCOUNT to understand ownership/nesting, not to prove durability.
9. SAVE TRANSACTION and Partial Rollback
A savepoint marks a location inside an active transaction.
BEGIN TRANSACTION;
INSERT INTO [dbo].[tblEvent]
([DT], [SQF_No], [ChargeNo], [Event_From], [Event_To])
VALUES
(SYSUTCDATETIME(), 2, 'CH-SAVEPOINT', 'Load', 'Heating');
SAVE TRANSACTION [BeforeOptionalCorrection];
UPDATE [dbo].[tblEvent]
SET [Temp_Act] = 9999.000
WHERE [ChargeNo] = 'CH-SAVEPOINT';
-- Undo only the optional correction.
ROLLBACK TRANSACTION [BeforeOptionalCorrection];
-- The INSERT before the savepoint remains part of this transaction.
COMMIT TRANSACTION;XACT_STATE() = -1, roll back the full transaction because writing the rollback-to-savepoint record is not allowed.10. Nested Transaction Behavior
BEGIN TRANSACTION; -- @@TRANCOUNT = 1
BEGIN TRANSACTION; -- @@TRANCOUNT = 2
COMMIT TRANSACTION; -- @@TRANCOUNT = 1; not yet finally committed
COMMIT TRANSACTION; -- @@TRANCOUNT = 0; outer transaction completesAn inner COMMIT only decrements @@TRANCOUNT. A ROLLBACK TRANSACTION without a savepoint rolls back the entire outer transaction and sets the count to zero.
11. Transaction Isolation Levels
Isolation controls which concurrent changes a transaction can observe and how reads interact with writes.
| Level | Dirty reads | Non-repeatable reads | Phantoms | Typical note |
|---|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible | Can display data that later rolls back |
| READ COMMITTED | Prevented | Possible | Possible | Common SQL Server default |
| REPEATABLE READ | Prevented | Prevented | Possible | Holds read locks longer |
| SNAPSHOT | Prevented | Prevented | Prevented | Uses row versions; database option required |
| SERIALIZABLE | Prevented | Prevented | Prevented | Strongest locking isolation; lowest concurrency |
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
-- Required statements
COMMIT TRANSACTION;Do not select an isolation level by name alone. Test correctness, blocking, version-store cost and throughput with the actual acquisition/report workload.
12. Row-Versioning Isolation
READ_COMMITTED_SNAPSHOT changes read-committed behavior to use row versions for many reads. SNAPSHOT gives a transaction-consistent view when enabled and requested.
Enabling row versioning is a database configuration change, not a query hint to add casually. Plan tempdb/version-store capacity, update-conflict handling, long readers and monitoring.
Avoid using NOLOCK as a universal “performance fix.” It permits dirty and inconsistent reads and does not solve every form of blocking.
13. Locks and Blocking
Writers acquire locks to protect changes. Another session may wait until the transaction commits or rolls back.
SELECT
r.[session_id], r.[status], r.[command],
r.[blocking_session_id], r.[wait_type], r.[wait_time],
r.[transaction_id]
FROM sys.dm_exec_requests AS r
WHERE r.[session_id] <> @@SPID
AND (r.[blocking_session_id] <> 0 OR r.[wait_type] IS NOT NULL);Reading this DMV requires appropriate diagnostic permissions. Solve blocking by shortening transactions, indexing predicates and reducing unnecessary work—not by killing sessions blindly.
14. Deadlocks and Bounded Retry
A deadlock occurs when sessions wait in a cycle. SQL Server chooses a victim and rolls that transaction back.
- Access tables and keys in a consistent order.
- Keep transactions short.
- Create indexes that avoid wide scans.
- Capture and analyze the deadlock graph.
- Retry the complete unit only for recognized transient errors.
- Use a bounded retry count with delay/jitter.
- Make the operation idempotent before retrying.
15. Why Long Transactions Hurt Runtime
| Cause | Impact | Better design |
|---|---|---|
| Waiting for operator confirmation | Locks remain open | Collect input before BEGIN |
| Calling PLC/API inside transaction | Database waits on network/device | Separate external work; use message/outbox design |
| Updating millions of rows at once | Log growth and blocking | Measured deterministic batches |
| Unconsumed query results | Connection/transaction cannot finish cleanly | Consume or close results before next step |
| Missing error cleanup | Orphan transaction in pooled connection | Rollback in catch/finally path |
16. Transactions Inside Stored Procedures
A simple procedure that owns its unit can use this pattern:
CREATE OR ALTER PROCEDURE [dbo].[usp_RecordTwoEvents]
@SQF_No int,
@ChargeNo varchar(50)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
INSERT dbo.tblEvent (DT, SQF_No, ChargeNo, Event_From, Event_To)
VALUES (SYSUTCDATETIME(), @SQF_No, @ChargeNo, 'Load', 'Heating');
INSERT dbo.tblEvent (DT, SQF_No, ChargeNo, Event_From, Event_To)
VALUES (DATEADD(MILLISECOND, 1, SYSUTCDATETIME()), @SQF_No, @ChargeNo, 'Heating', 'Soaking');
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
THROW;
END CATCH;
END;
GOIf callers may already own a transaction, use a reviewed composable pattern with the entry @@TRANCOUNT and a savepoint. Do not improvise nested commits.
17. Transactions from ODBC, ADO or Python
A client transaction belongs to one live connection. Every command in the unit must use that same connection and transaction object.
Open connection
Begin transaction on that connection
Try:
Execute parameterized INSERT
Execute parameterized UPDATE
Validate returned ID and affected-row counts
Commit
Catch:
Roll back if transaction is active
Record the original error
Finally:
Close/dispose the transaction and connectionDo not place GO inside an application command. Do not open a second connection for one of the statements and assume it joins the first local transaction.
18. Connection Loss and Idempotency
The network can fail after SQL Server commits but before the client receives confirmation. Retrying a plain INSERT can create a duplicate.
-- Example source-generated event key protected by a UNIQUE constraint.
INSERT INTO [dbo].[tblEventInbox]
([SourceEventID], [ReceivedUtc], [Payload])
VALUES
(@SourceEventID, SYSUTCDATETIME(), @Payload);Generate a stable source event ID before the first attempt, enforce uniqueness and return the existing result when a retry presents the same key. The key must identify the same logical event, not a new attempt.
19. Local vs Distributed Transactions
A local transaction covers one SQL Server connection/resource manager. Accessing linked servers or multiple transactional resources can promote the unit to a distributed transaction that depends on additional infrastructure and has different failure modes.
- Prefer one database-owned procedure when possible.
- Avoid network calls inside the transaction.
- Use an outbox/message pattern for reliable cross-system workflows.
- Test coordinator outages and recovery if distributed transactions are unavoidable.
- Remember: savepoint rollback is not supported in distributed transactions.
20. Verify Transaction Behavior
-- Verify no transaction remains open in the current session.
SELECT XACT_STATE() AS [XactState], @@TRANCOUNT AS [TranCount];
-- Verify the logical unit.
SELECT [ID], [DT], [SQF_No], [ChargeNo], [Event_From], [Event_To]
FROM [dbo].[tblEvent]
WHERE [ChargeNo] = @ChargeNo
ORDER BY [DT], [ID];
-- During a controlled test, inspect the oldest active transaction.
DBCC OPENTRAN (N'SQF_DB');XACT_STATE() = 0 and @@TRANCOUNT = 0 on the connection.21. Common TCL Errors and Fixes
| Symptom | Likely cause | Check or fix |
|---|---|---|
| Transaction count mismatch | Procedure changed caller-owned transaction count | Define ownership and use a composable savepoint pattern |
| Cannot commit; transaction uncommittable | XACT_STATE() = -1 | Full rollback, then rethrow original error |
| Locks persist after screen action | Open transaction waiting on UI/network | Collect inputs before transaction; ensure cleanup |
| Duplicate after timeout | Commit succeeded but response was lost | Use a stable idempotency key |
| Deadlock victim | Conflicting lock order | Analyze graph, index/order consistently, bounded retry |
| Rollback to savepoint fails | Distributed or uncommittable transaction | Roll back entire transaction |
| Inner COMMIT did not release locks | Outer transaction still active | Inspect @@TRANCOUNT; outer owner must finish |
| Unexpected dirty values | READ UNCOMMITTED/NOLOCK | Use an isolation level matching correctness needs |
22. Complete Practical TCL Lab
Hands-on- Use a disposable
SQF_DBcopy. - Open two SSMS windows for blocking tests.
- Never leave either session with
@@TRANCOUNT > 0.
Observe autocommit
Insert one lab event without an explicit transaction and confirm it is immediately visible from the second session.
Rehearse full rollback
BEGIN TRANSACTION;
INSERT dbo.tblEvent (DT, SQF_No, ChargeNo, Event_From, Event_To)
VALUES (SYSUTCDATETIME(), 2, 'CH-TCL-LAB', 'Load', 'Heating');
SELECT XACT_STATE() AS XactState, @@TRANCOUNT AS TranCount;
ROLLBACK TRANSACTION;Commit a two-statement unit
Insert a new transition and update the previous event by ID. Require exactly one updated row, then commit.
Use a savepoint
Insert a valid event, save a point, perform an optional correction, roll back to the savepoint and commit the valid insert.
Observe blocking safely
In session A, update one lab row inside a transaction without committing. In session B, query/update the same key and observe waiting. Roll back session A immediately and confirm session B proceeds.
Test the error path
Run the TRY/CATCH template with a deliberately invalid foreign key in the test database. Confirm the complete unit rolls back, the original error is rethrown and no transaction remains open.
You can now define a transaction unit, commit or roll back correctly, inspect state/count, use savepoints, observe blocking and build a reliable error path.
TCL Deployment Checklist
- Define one business unit of work.
- Choose the transaction owner.
- Collect external inputs before BEGIN.
- Use parameterized DML and exact keys.
- Enable predictable error handling.
- Validate OUTPUT and row counts.
- Commit quickly or fully roll back.
- Verify no transaction remains open.
- Test blocking, deadlock, timeout and disconnect.
- Make retry-safe operations idempotent.
Further Reading
Frequently Asked Questions
What is TCL in SQL Server?
TCL controls transaction boundaries with BEGIN TRANSACTION, COMMIT, ROLLBACK and SAVE TRANSACTION.
What is the difference between COMMIT and ROLLBACK?
COMMIT accepts a valid unit, while ROLLBACK undoes changes to the transaction start or a savepoint.
What does XACT_STATE return?
It returns 1 for a committable transaction, 0 for no transaction and -1 for an uncommittable transaction.
Does an inner COMMIT make changes permanent?
No. It only decrements the transaction count; the outermost COMMIT completes the transaction.
How long should a SCADA transaction remain open?
As briefly as possible. Never wait for operator input, PLC tags or external network responses while holding it open.
