Identifying causes for deadlock and ways to prevent it - azure-sql-database

We have an ELT process wherein we transfer data from Azure Data Factory into a staging table present in Azure SQL database and then trigger a stored procedure that alters schema from staging to the final version.
The above process executes in parallel for multiple entities (at the same time).
But sometimes the job fails intermittently with failure message as deadlock.
Note: we have enabled Azure Log Analytics for Azure SQL database logs.
I have these questions:
Are there any metadata queries like sys.logs to determine the cause of deadlock and jobs getting executed at that instance.
How to identify the exact time and the cause of deadlock leveraging Azure log analytics (KQL queries)
ALTER SCHEMA uses a schema level lock. So, my understanding is the deadlock might be due to this reason but is there a sure shot way to confirm this is the cause for deadlock as the failures are intermittent and not always.
Is there any sys. queries to identify whether the schema is in lock state or not, so we can check the state of schema before altering it in case of parallel executions assuming #3 is the cause.
Is there any correlation between deadlock occurrence and database tier? Because we observed that after increasing the DB tier, the intermittent failure frequency reduced.

After execution of a workload on the server, issue the following statements in Query Editor to find queries still holding locks.
--
-- The pair matching targets report current unpaired events using
-- the sys.dm_xe_session_targets dynamic management view (DMV)
-- in XML format.
-- The following query retrieves the data from the DMV and stores
-- key data in a temporary table to speed subsequent access and
-- retrieval.
--
SELECT
objlocks.value('(action[#name="session_id"]/value)[1]', 'int')
AS session_id,
objlocks.value('(data[#name="database_id"]/value)[1]', 'int')
AS database_id,
objlocks.value('(data[#name="resource_type"]/text)[1]', 'nvarchar(50)' )
AS resource_type,
objlocks.value('(data[#name="resource_0"]/value)[1]', 'bigint')
AS resource_0,
objlocks.value('(data[#name="resource_1"]/value)[1]', 'bigint')
AS resource_1,
objlocks.value('(data[#name="resource_2"]/value)[1]', 'bigint')
AS resource_2,
objlocks.value('(data[#name="mode"]/text)[1]', 'nvarchar(50)')
AS mode,
objlocks.value('(action[#name="sql_text"]/value)[1]', 'varchar(MAX)')
AS sql_text,
CAST(objlocks.value('(action[#name="plan_handle"]/value)[1]', 'varchar(MAX)') AS xml)
AS plan_handle,
CAST(objlocks.value('(action[#name="tsql_stack"]/value)[1]', 'varchar(MAX)') AS xml)
AS tsql_stack
INTO #unmatched_locks
FROM (
SELECT CAST(xest.target_data as xml)
lockinfo
FROM sys.dm_xe_session_targets xest
JOIN sys.dm_xe_sessions xes ON xes.address = xest.event_session_address
WHERE xest.target_name = 'pair_matching' AND xes.name = 'FindBlockers'
) heldlocks
CROSS APPLY lockinfo.nodes('//event[#name="lock_acquired"]') AS T(objlocks)
--
- Join the data acquired from the pairing target with other
- DMVs to return provide additional information about blockers
SELECT ul.*
FROM #unmatched_locks ul
INNER JOIN sys.dm_tran_locks tl ON ul.database_id = tl.resource_database_id AND
ul.resource_type = tl.resource_type
WHERE resource_0 IS NOT NULL
AND session_id IN
(SELECT blocking_session_id FROM sys.dm_exec_requests WHERE
blocking_session_id != 0)
AND tl.request_status='wait'
AND REPLACE(ul.mode, 'LCK_M_', '' ) = tl.request_mode
https://learn.microsoft.com/en-us/sql/relational-databases/extended-events/determine-which-queries-are-holding-locks?view=sql-server-ver16

Related

SQL Server process never ends

I have a process in sql server that seems to never end. To spot if there is a block in this process I used EXEC sp_who2 and I seen that the SPID is 197.
The status is runnable and there is no block. Command is inserting. The weird thing is the CPU time which is the biggest: 68891570 and the DISK IO operations: 16529185.
This process truncates two tables and then insert the data from a another table to these two tables. It is true that there is a lot of information (101962758 rows in the origin table) but I think that there is too much time.
What can I do to accelerate this process or to spot what is happening?
Thank you
It depends on the scenario here. I recommend the following steps in order to decide where to move next.
Find most expensive queries
Using the following SQL to determine the most expensive queries:
SELECT TOP 10 SUBSTRING(qt.TEXT, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.TEXT)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1),
qs.execution_count,
qs.total_logical_reads, qs.last_logical_reads,
qs.total_logical_writes, qs.last_logical_writes,
qs.total_worker_time,
qs.last_worker_time,
qs.total_elapsed_time/1000000 total_elapsed_time_in_S,
qs.last_elapsed_time/1000000 last_elapsed_time_in_S,
qs.last_execution_time,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_logical_reads DESC -- logical reads
-- ORDER BY qs.total_logical_writes DESC -- logical writes
-- ORDER BY qs.total_worker_time DESC -- CPU time
Execution plan
This could help to determine what is going on with your actual query. More information could be found here.
Performance tips
Indexes. Remove all indexes, except for those needed by the insert (SELECT INTO)
Constraints and triggers. Remove them from the table.
Choosing good clustered index. New records will be inserted at the end of the table.
Fill factor. Set it to 0 or 100 (the same as 0). This will reduce the number of pages that the data is spread across.
Recovery model. Change it to Simple.
Also
Consider reviewing Insert into table select * from table vs bulk insert.
From the info you provided,it seems the query is still running...
This process truncates two tables and then insert the data from a another table to these two tables. It is true that there is a lot of information (101962758 rows in the origin table) but I think that there is too much time.
Can you follow below process (instead of your approach), assuming your tables are Main, T1, T2.
Select * into t1_dup,t2_dup from main
rename t1,t2 to t1dup,t2dup
rename t1_dup,t2_dup to t1,t2
drop t1dup,t2dup

Best incremental load method using SSIS with over 20 million records

What is needed: I'm needing 25 million records from oracle incrementally loaded to SQL Server 2012. It will need to have an UPDATE, DELETE, NEW RECORDS feature in the package. The oracle data source is always changing.
What I have: I've done this many times before but not anything past 10 million records.First I have an [Execute SQL Task] that is set to grab the result set of the [Max Modified Date]. I then have a query that only pulls data from the [ORACLE SOURCE] > [Max Modified Date] and have that lookup against my destination table.
I have the the [ORACLE Source] connecting to the [Lookup-Destination table], the lookup is set to NO CACHE mode, I get errors if I use partial or full cache mode because I assume the [ORACLE Source] is always changing. The [Lookup] then connects to a [Conditional Split] where I would input an expression like the one below.
(REPLACENULL(ORACLE.ID,"") != REPLACENULL(Lookup.ID,""))
|| (REPLACENULL(ORACLE.CASE_NUMBER,"")
!= REPLACENULL(ORACLE.CASE_NUMBER,""))
I would then have the rows that the [Conditional Split] outputs into a staging table. I then add a [Execute SQL Task] and perform an UPDATE to the DESTINATION-TABLE with the query below:
UPDATE Destination
SET SD.CASE_NUMBER =UP.CASE_NUMBER,
SD.ID = UP.ID,
From Destination SD
JOIN STAGING.TABLE UP
ON UP.ID = SD.ID
Problem: This becomes very slow and takes a very long time and it just keeps running. How can I improve the time and get it to work? Should I use a cache transformation? Should I use a merge statement instead?
How would I use the expression REPLACENULL in the conditional split when it is a data column? would I use something like :
(REPLACENULL(ORACLE.LAST_MODIFIED_DATE,"01-01-1900 00:00:00.000")
!= REPLACENULL(Lookup.LAST_MODIFIED_DATE," 01-01-1900 00:00:00.000"))
PICTURES BELOW:
A pattern that is usually faster for larger datasets is to load the source data into a local staging table then use a query like below to identify the new records:
SELECT column1,column 2
FROM StagingTable SRC
WHERE NOT EXISTS (
SELECT * FROM TargetTable TGT
WHERE TGT.MatchKey = SRC.MatchKey
)
Then you just feed that dataset into an insert:
INSERT INTO TargetTable (column1,column 2)
SELECT column1,column 2
FROM StagingTable SRC
WHERE NOT EXISTS (
SELECT * FROM TargetTable TGT
WHERE TGT.MatchKey = SRC.MatchKey
)
Updates look like this:
UPDATE TGT
SET
column1 = SRC.column1,
column2 = SRC.column2,
DTUpdated=GETDATE()
FROM TargetTable TGT
WHERE EXISTS (
SELECT * FROM TargetTable SRC
WHERE TGT.MatchKey = SRC.MatchKey
)
Note the additional column DTUpdated. You should always have a 'last updated' column in your table to help with auditing and debugging.
This is an INSERT/UPDATE approach. There are other data load approaches such as windowing (pick a trailing window of data to be fully deleted and reloaded) but the approach depends on how your system works and whether you can make assumptions about data (i.e. posted data in the source will never be changed)
You can squash the seperate INSERT and UPDATE statements into a single MERGE statement, although it gets pretty huge, and I've had performance issues with it and there are other documented issues with MERGE
Unfortunately, there's not a good way to do what you're trying to do. SSIS has some controls and documented ways to do this, but as you have found they don't work as well when you start dealing with large amounts of data.
At a previous job, we had something similar that we needed to do. We needed to update medical claims from a source system to another system, similar to your setup. For a very long time, we just truncated everything in the destination and rebuilt every night. I think we were doing this daily with more than 25M rows. If you're able to transfer all the rows from Oracle to SQL in a decent amount of time, then truncating and reloading may be an option.
We eventually had to get away from this as our volumes grew, however. We tried to do something along the lines of what you're attempting, but never got anything we were satisfied with. We ended up with a sort of non-conventional process. First, each medical claim had a unique numeric identifier. Second, whenever the medical claim was updated in the source system, there was an incremental ID on the individual claim that was also incremented.
Step one of our process was to bring over any new medical claims, or claims that had changed. We could determine this quite easily, since the unique ID and the "change ID" column were both indexed in source and destination. These records would be inserted directly into the destination table. The second step was our "deletes", which we handled with a logical flag on the records. For actual deletes, where records existed in destination but were no longer in source, I believe it was actually fastest to do this by selecting the DISTINCT claim numbers from the source system and placing them in a temporary table on the SQL side. Then, we simply did a LEFT JOIN update to set the missing claims to logically deleted. We did something similar with our updates: if a newer version of the claim was brought over by our original Lookup, we would logically delete the old one. Every so often we would clean up the logical deletes and actually delete them, but since the logical delete indicator was indexed, this didn't need to be done too frequently. We never saw much of a performance hit, even when the logically deleted records numbered in the tens of millions.
This process was always evolving as our server loads and data source volumes changed, and I suspect the same may be true for your process. Because every system and setup is different, some of the things that worked well for us may not work for you, and vice versa. I know our data center was relatively good and we were on some stupid fast flash storage, so truncating and reloading worked for us for a very, very long time. This may not be true on conventional storage, where your data interconnects are not as fast, or where your servers are not colocated.
When designing your process, keep in mind that deletes are one of the more expensive operations you can perform, followed by updates and by non-bulk inserts, respectively.
Incremental Approach using SSIS
Get Max(ID) and Max(ModifiedDate) from Destination Table and Store them in a Variables
Create a temporary staging table using EXECUTE SQL TASK and store that temporary staging table name into the variable
Take a Data Flow Task and Use OLEDB Source and OLEDB Destination to pull the data from the Source System and load the
data into the variable of temporary tables
Take Two Execute SQL Task one for Insert Process and other for Update
Drop the Temporary Table
INSERT INTO sales.salesorderdetails
(
salesorderid,
salesorderdetailid,
carriertrackingnumber ,
orderqty,
productid,
specialofferid,
unitprice,
unitpricediscount,
linetotal ,
rowguid,
modifieddate
)
SELECT sd.salesorderid,
sd.salesorderdetailid,
sd.carriertrackingnumber,
sd.orderqty,
sd.productid ,
sd.specialofferid ,
sd.unitprice,
sd.unitpricediscount,
sd.linetotal,
sd.rowguid,
sd.modifieddate
FROM ##salesdetails AS sd WITH (nolock)
LEFT JOIN sales.salesorderdetails AS sa WITH (nolock)
ON sa.salesorderdetailid = sd.salesorderdetailid
WHERE NOT EXISTS
(
SELECT *
FROM sales.salesorderdetails sa
WHERE sa.salesorderdetailid = sd.salesorderdetailid)
AND sa.salesorderdetailid > ?
UPDATE sa
SET SalesOrderID = sd.salesorderid,
CarrierTrackingNumber = sd.carriertrackingnumber,
OrderQty = sd.orderqty,
ProductID = sd.productid,
SpecialOfferID = sd.specialofferid,
UnitPrice = sd.unitprice,
UnitPriceDiscount = sd.unitpricediscount,
LineTotal = sd.linetotal,
rowguid = sd.rowguid,
ModifiedDate = sd.modifieddate
FROM sales.salesorderdetails sa
LEFT JOIN ##salesdetails sd
ON sd.salesorderdetailid = sa.salesorderdetailid
WHERE sa.modifieddate > sd.modifieddate
AND sa.salesorderdetailid < ?
Entire Process took 2 Minutes to Complete
Incremental Process Screenshot
I am assuming you have some identity like (pk)column in your oracle table.
1 Get max identity (Business key) from Destination database (SQL server one)
2 Create two data flow
a) Pull only data >max identity from oracle and put them Destination directly .( As these are new record).
b) Get all record < max identity and update date > last load put them into temp (staging ) table (as this is updated data)
3 Update Destination table with record from temp table record (created at step b)

SQL Server : distributed transaction and duplicated rows

I have 1 core SQL Server and many secondary SQL Servers that transfer data to the core server.
Every secondary SQL Server has linked core server and stored procedure that runs from time to time.
This is the code from a stored procedure (some fields are deleted, but it's not improtant)
BEGIN DISTRIBUTED TRANSACTION
SELECT TOP (#ReceiptsQuantity)
MarketId, CashCheckoutId, ReceiptId, GlobalReceiptId
INTO #Receipts
FROM dbo.Receipt
WHERE Transmitted = 0
SELECT ReceiptId, Barcode, GoodId
INTO #ReceiptGoodsStrings
FROM ReceiptGoodsStrings
WHERE ReceiptGoodsStrings.ReceiptId in (SELECT ReceiptId FROM #Receipts)
INSERT INTO [SyncServer].[POSServer].[dbo].[Receipt]
SELECT * FROM #Receipts
INSERT INTO [SyncServer].[POSServer].[dbo].[ReceiptGoodsStrings]
SELECT * FROM #ReceiptGoodsStrings
UPDATE Receipt
SET Transmitted = 1
WHERE ReceiptId in (SELECT ReceiptId FROM #Receipts)
DROP TABLE #Receipts
DROP TABLE #ReceiptGoodsStrings
COMMIT TRANSACTION
There are s two tables: Receipts has many ReceiptGoodsStrings (key ReceiptID)
It's working fine. But sometimes on core server I have duplicated rows in Receipts and ReceiptGoodsStrings. It's happening very rarely and I cannot understand why.
Maybe I chose the wrong way to transfer data?
It seems it is a concurrency problem.
There is a possibility that two concurrent transaction open and both read from your Receipt table. Each session is going to write to its own temp tables (#Receipts and #ReceiptGoodsStrings). At the end, clients intermittently lock [SyncServer].[POSServer].[dbo].[Receipt] and [SyncServer].[POSServer].[dbo].[ReceiptGoodsStrings] to stuff rows from temp tables to destination and both of them perform an update.
Thus, both transactions are succesfully completed and you have duplicate rows!
Fortunately, you can use UPDLOCK hint on your first select from Receipt table to lock the rows/pages you already read while inside a transaction. The other client will have to wait for the lock to be released by the first client performing COMMIT. Then, second one will continue, read only new rows to be transmitted and copy them and only them.
SELECT TOP (#ReceiptsQuantity)
MarketId, CashCheckoutId, ReceiptId, GlobalReceiptId
INTO #Receipts
FROM dbo.Receipt WITH (UPDLOCK)
WHERE Transmitted = 0
EDIT
At the end, pay attention to the interval you use to call the sync transaction. It may be that the interval is too short, so the transaction is not yet finished while the new one is starting. In this case you can expect to get the duplicated rows because. You could try to increase the interval.

How do I optimize writing to a large remote table?

I'm working with a set of servers that all record an event that typically occurs multiple times a day, then later call a stored procedure to copy those records to a matching table on a central remote server. The key part of that stored procedure is as follows:
First, because the events take several minutes, sometimes they won't be complete when they're copied and certain records in the central server will have null values in certain columns. The stored procedure updates the records this happened to last time:
UPDATE r SET r.ToUpdateA = l.ToUpdateA, r.ToUpdateB = l.ToUpdateB
FROM LocalTable l INNER JOIN RemoteServer.RemoteDB.dbo.RemoteTable r
ON l.IdentifierA = r.IdentifierA AND l.IdentifierB = r.IdentifierB
WHERE r.ToUpdateB IS NULL AND l.ToUpdateB IS NOT NULL;
Both IdentifierA and IdentifierB are necessary to identify a given record; the first identifies which server it's from.
Second comes the update itself, identifying records on the local table that aren't on the remote table and inserting them:
INSERT INTO RemoteServer.RemoteDB.dbo.RemoteTable (A, B, C...)
SELECT l.A, l.B, l.C...
FROM LocalTable l LEFT OUTER JOIN RemoteServer.RemoteDB.dbo.RemoteTable r
ON l.IdentifierA = r.IdentifierA AND l.IdentifierB = r.IdentifierB
WHERE r.uid IS NULL;
These joins are coming to take too long as the central remote table grows, especially on the larger servers. The estimated execution plan indicates that most of the work is being done in a Remote Scan for the UPDATE's inner join (relating to the r.ToUpdateB IS NULL part) and a Remote Query for the INSERT's left outer join (selecting three columns from the entire RemoteTable). I can think of three types of solutions:
Delete old records. We've never needed to look further back than a month or so.
Split the work between stored procedures on the "spoke" and "hub" servers. This would mean just copying new records blindly to a new intermediate table on the "hub", perhaps with an extra BIT column on the "spokes" to indicate whether a given record has been copied, and having the "hub" weed out duplicates itself.
Modify the joins to be faster. This is what I'd like to do if possible -- there's probably a way to sent the recent data to the hub server and instruct it on what to do with it, all on the same query, rather than fetching massive amounts of data from the hub. I tried changing INNER JOIN to INNER REMOTE JOIN, but if I'm interpreting the modified execution plan right, that would take orders of magnitude longer.
Is #3 feasible? If so, how?
The best way, by far, that I have found to dramastically increase performance on Linked Server DML statements is to not do them ;-). Fortunately I am being more cheeky than sarcastic :).
The trick is to do the DML work on the server where the table lives. In order to do that you:
gather the related/relevant data
package it up as XML (but stored in a NVARCHAR(MAX) variable since XML is not a valid datatype for Linked Server calls)
execute a proc on the remote server, passing in that dataset, that unpacks the XML into a Temporary Table and joins to that (hence a local transaction).
I have this method detailed in two answers:
Cross Server Transaction taking too long inside a procedure
DELETE from Linked Server table using OPENQUERY and dynamic criteria
The method described above deals with how to transfer data over faster, but doesn't address an improvement that can be made on identifying what data to move over in the first place.
Scanning the destination table, even if it were merely in a different database on the same instance, each time to determine missing records is very expensive as row counts increase. This expensive can be avoided by dumping new records into a queue table. This queue table holds only the records that need to be inserted and potentially updated. Once you know that the records have been synced remotely, you remove those records. This is similar to your option #3 in the Question, but not doing so all in a single query as there is no way to identify the "new" records outside of scanning the destination table (simple but doesn't scale) or capturing them as they come in (a little more effort but scales quite well).
The queue table can be either:
a user created table that is populated via an INSERT trigger. This table can be just the key fields and a status (needed to keep track of the INSERT vs potential UPDATE)
a system table created by enabling Change Data Capture (CDC) or Change Tracking on the source table
In either case, you would do something along the lines of:
Create the queue table
CREATE TABLE RemoteTableQueue
(
RemoteTableQueueID INT NOT NULL IDENTITY(-2140000000, 1)
CONSTRAINT [PK_RemoteTableQueue] PRIMARY KEY,
IdentifierA DATATYPE NOT NULL,
IdentifierB DATATYPE NOT NULL,
StatusID TINYINT NOT NULL,
);
Create an AFTER INSERT trigger
INSERT INTO RemoteTableQueue (IdentifierA, IdentifierB, StatusID)
SELECT IdentifierA, IdentifierB, 1
FROM INSERTED;
Update your ETL proc (assuming this is single-threaded)
CREATE TABLE #TempUpdate
(
IdentifierA DATATYPE NOT NULL,
IdentifierB DATATYPE NOT NULL,
ToUpdateA DATATYPE NOT NULL,
ToUpdateB DATATYPE NOT NULL
);
BEGIN TRAN;
INSERT INTO #TempUpdate (IdentifierA, IdentifierB, ToUpdateA, ToUpdateB)
SELECT lt.IdentifierA, lt.IdentifierB, lt.ToUpdateA, lt.ToUpdateB
FROM LocalTable lt
INNER JOIN RemoteTableQueue rtq
ON lt.IdentifierA = rtq.IdentifierA
AND lt.IdentifierB = rtq.IdentifierB
WHERE rtq.StatusID = 2 -- rows eligible for UPDATE
AND lt.ToUpdateB IS NOT NULL;
DECLARE #UpdateData NVARCHAR(MAX);
SET #UpdateData = (
SELECT *
FROM #TempUpdate
FOR XML ...);
EXEC RemoteServer.RemoteDB.dbo.UpdateProc #UpdateData;
DELETE rtq
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB;
TRUNCATE TABLE #TempUpdate;
INSERT INTO #TempUpdate (IdentifierA, IdentifierB, ToUpdateA, ToUpdateB)
SELECT lt.IdentifierA, lt.IdentifierB, lt.ToUpdateA, lt.ToUpdateB
FROM LocalTable lt
INNER JOIN RemoteTableQueue rtq
ON lt.IdentifierA = rtq.IdentifierA
AND lt.IdentifierB = rtq.IdentifierB
WHERE rtq.StatusID = 1 -- rows to INSERT;
SET #UpdateData = (
SELECT lt.*
FROM LocalTable lt
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
FOR XML ...);
EXEC RemoteServer.RemoteDB.dbo.InsertProc #UpdateData;
-- no need to check for changed value later if it already has it now
DELETE rtq
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
WHERE tmp.ToUpdateB IS NOT NULL;
-- we know these records will need to be checked later since they are NULL
UPDATE rtq
SET rtq.StatusID = 2 -- rows eligible for UPDATE
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
WHERE tmp.ToUpdateB IS NULL;
COMMIT;
Additional Steps
Add TRY / CATCH logic to ETL proc to properly handle ROLLBACK
Update remote INSERT and UPDATE procs to batch the incoming data into the destination table (loop through temp table populated from incoming XML, processing 1000 rows at a time until done).
If there is too much contention between "spoke" servers reporting in at the same time, create an incoming Queue table on the Remote server that the incoming XML data simply gets inserted into with no additional logic. That is a very clean and quick operation. Then create a local job on the Remote server to check every few minutes and if rows exist in the incoming Queue table, process them into the destination table. This separates the transactions between the Source server/table and the Destination server/table, thereby reducing contention.
The [RemoteTableQueueID] field exists in case you change your ETL model to just run every 3 - 10 minutes all day long, grabbing the TOP (#BatchSize) of rows to process, in which case you would want to ORDER BY [RemoteTableQueueID] ASC

How do I set isolation levels so that once one transaction completes the inserted data is available to another transaction?

I have the following tables (greatly simplified):
Jobs: JobId, JobState
Data: DataId
JobsData: JobId, DataId
the idea for JobsData is that any item in Data can be associated to one or more item in Jobs and each item in Jobs can have one or more item from Data associated with it.
Now I have two transactions:
-- TRAN1
BEGIN TRAN
INSERT INTO Data VALUES (NewDataId)
INSERT INTO Jobs VALUES (NewJobId, StateInitial)
INSERT INTO JobsData VALUES (NewDataId, NewJobId)
UPDATE Jobs SET JobState=StateReady WHERE JobId=NewJobId
COMMIT TRAN
-- TRAN2
DECLARE #selectedId;
SELECT TOP (1) #selectedId=JobId FROM Jobs WITH (UPDLOCK, READPAST) WHERE JobState=StateReady
IF #selectedId IS NOT NULL
SELECT DataId FROM JobsData WHERE JobId = #selectedId
The code with the locking hints comes from this answer. Its purpose is to have multiple TRAN2 instances running in parallel and never getting the same JobId.
That code has been working fine with SQL Server (default isolation level READ_COMMITTED) but in SQL Azure TRAN2 sometimes works incorrectly - the first SELECT yields a non-null JobId, but the second SELECT yields and empty resultset. I assume this is because the default isolation level in SQL Azure is READ_COMMITTED_SNAPSHOT.
I want to make minimum changes to get my problem resolved - so that TRAN2 either retrieves null in the first SELECT or retrieves the right result set in the second SELECT.
Which of the table hits do I apply to which of the SQL statements I have?
For a starter, if you want queues in Azure, use Azure Queues or Service Bus Queues.
If you insist on implementing queues over relations, use the pattern from Using Tables as Queues. Specifically:
do not use state fields instead of event queues ('Ready' is a record in your jobs queue, an event, not a state on the job).
dequeue using DELETE ... WITH OUTPUT ...
Trust me on this one.