Use of explicit transaction is common in SQL Server development. Sometimes a developer might inadvertently specify a Data Definition Language (including SELECT INTO) clause within a long running explicit transaction, similar to the structure below, within a SQL Server object such as a stored procedure.
CREATEPROCEDURE ... AS BEGINTRAN -- ManyDML (UPDATE\INSERT\DELETE) SELECTcolumnlist, .. INTO #temp FROMdbo.LargeTable WHERE ... -- Long runningDML (UPDATE\INSERT\DELETE) ... (COMMITOR ROLLBACK)This article describes the side effect of having DDL (including SELECT INTO) within a long running transaction.
DDL and TransactionData Definition Language (DDL) statements such as CREATE TABLE honors transactions. So if a BEGIN TRAN is specified, followed by a CREATE TABLE and then a ROLLBACK, then the table will not be created.
SELECT INTO works similarly to CREATE TABLE because it creates a new table with the columns returned by the SELECT statement. Under the cover, it begins an implicit transaction and populates the related system tables with the necessary rows to define the table structure same as a CREATE TABLE DDL.
When a DDL (including SELECT INTO) statement is specified within an explicit transaction, the underlying row(s) in the affected system table will be exclusively locked during this duration until the transaction is explicitly committed.
An inadvertent effect due to locked row(s) in the affected system tables are blockings or timeout on monitoring queries, regular administrative tasks and queries which require to scan all rows in the affected system tables. This article will demonstrate the side effects on SQL Server 2016 Developer Edition Service Pack 1.
Test Environment We will create a test database and interrogate the transaction log using the undocumented system function fn_dblog. The column [Transaction ID] contains the system degenerate transaction ID for all logged operations. The same value in the [Transaction ID] column means the operations logged belongs to the same transaction scope. CREATEDATABASEtest GO USE test GO ALTERDATABASEtestSETRECOVERYSIMPLE GO CHECKPOINT GO WAITFORDELAY '00:00:10' -- Allowtimefor checkpoint GO CREATETABLEdbo.test (col1INT) GO DECLARE @transactionidnvarchar(28) SELECT @transactionid = [transactionid] FROMfn_dblog(null,null) WHERE [TransactionName] = 'CREATE TABLE' SELECTOperation, Context, AllocUnitName, COUNT(*) OpCount FROMfn_dblog(null,null) WHERE [TransactionID] = @transactionid GROUPBYOperation, Context, AllocUnitNameImage may be NSFW.
Clik here to view.

A wealth of information can be obtained from the transaction log using system function fn_dblog. The screenshot above shows us the system tables involved when a CREATE TABLE is executed.
LOP_BEGIN_XACT is the (implicit) begin transaction.
LOP_COMMIT_XACT is the (implicit) commit transaction.
Explicit Transaction and SELECT INTOIf we specify an explicit begin transaction, the SQL Server transaction log still logs this as LOP_BEGIN_XACT. A manual COMMIT TRAN will generate LOP_COMMIT_XACT similarly to an implicit commit transaction.
We will use SELECT INTO in this contrived example, because this is the more commonly used in SQL Server development.
We will start an explicit transaction and use SELECT INTO to create a temp table.
When SELECT INTO is executed into a temporary table, the transaction will be logged in tempdb even though the context of the database is on a user database. So in this scenario, the fn_dblog function is executed in the context of tempdb to obtain details of the SELECT INTO transaction. When we interrogate the transaction log, we would see a LOP_BEGIN_XACT but without the corresponding LOP_COMMIT_XACT because the transaction is still uncommitted.
A lesser-known trick is that SQL Server allows you to label an explicit transaction with a name. This transaction name is recorded in the transaction log [Transaction Name] column. This would then allow fn_dblog to easily search and identify all rows which belongs to the same transaction scope using the transaction name. In the code example below, we have an explicit transaction name “tmp” which gets stamps into the transaction log to allow us to filter the result by [Transaction ID]. USE test GO BEGINTRANtmp SELECT * INTO #temp FROMtest GO -- COMMITTRANtmp USE tempdb GO CHECKPOINT GO DECLARE @transactionidnvarchar(28) SELECT @transactionid = [TransactionID] FROMfn_dblog(null,null) WHERE [TransactionName] = 'tmp' SELECT Operation, Context, AllocUnitName, COUNT(*) OpCount FROMfn_dblog(null, null) WHERE [TransactionID] = @transactionid AND OperationIN ('LOP_BEGIN_XACT', 'LOP_COMMIT_XACT') GROUPBYOperation, Context, AllocUnitNameImage may be NSFW.
Clik here to view.

As expected, we can see the SELECT INTO started a transaction, but do not have a corresponding commit transaction (LOP_COMMIT_XACT) operation yet. It just means that the transaction is not committed yet.
Locks in System TablesIf you check for locks, there will be a bunch of Intent Exclusive locks on system tables and Exclusive locks on index rows (KEYS), PAGE and EXTENT.
The locks are against system tables in tempdb since the SELECT INTO automatically goes into tempdb as a temp table. This query below needs to be executed within the same session that started the transaction, otherwise the query will be blocked if executing on a different session.
If you want to execute the query from another session, then you need to set the new session isolation level to read uncommitted (Syntax:SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED) SELECT t1.resource_type , t1.resource_database_id , t1.resource_associated_entity_id , t1.request_mode , t1.request_session_id , o1.name 'object name' , o1.type_desc 'object descr' FROMsys.dm_tran_locksas t1 LEFTOUTERJOINsys.objectso1 ONo1.object_id = t1.resource_associated_entity_id LEFTOUTERJOINsys.partitionsp1 ONp1.hobt_id = t1.resource_associated_entity_id LEFTOUTERJOINsys.allocation_unitsa1 ONa1.allocation_unit_id = t1.resource_associated_entity_id WHEREt1.request_modeIN ('IX', 'X')Image may be NSFW.
Clik here to view.

Locking and Blockings
We will now perform a simple operation in SQL Server Management Studio (SSMS), checking the database properties of tempdb.
Image may be NSFW.
Clik here to view.

Typically the Database Properties form would show immediately. But in our scenario, SSMS would be in a “busy” state and not responding.
So, we will launch a new SSMS and execute the query below to check for blockings.
SELECT command , r.status , blocking_session_id , wait_type, wait_resource , [program_name], [text] FROMsys.dm_exec_requests r JOINsys.dm_exec_sessions s on r.session_id = s.session_id CROSSAPPLYsys.dm_exec_sql_text(r.sql_handle) WHERE r.session_id <> @@SPIDImage may be NSFW.
Clik here to view.

The output reveals that we have a blocking session id 58 which is executing the SELECT INTO query. The waitresource 2:327680 is system table sysrowsets in tempdb.
The Database Properties form is unable to launch because the query which calculates the database size is blocked. We might not be interested in checking tempdb database size, but this is one of the side effect when row(s) in system table are locked exclusively.
If you wait long enough, the error m