GET occurs far more frequently than CREATE, so it makes sense to optimize for that.
IF EXISTS (SELECT * FROM MyTable WHERE Id=@Id)
SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
ELSE
BEGIN
BEGIN TRAN
IF NOT EXISTS (SELECT * FROM MyTable WITH (HOLDLOCK, UPDLOCK) WHERE Id=@Id )
BEGIN
-- WAITFOR DELAY '00:00:10'
INSERT INTO MyTable (Id, X, Y, Z) VALUES (@Id, @X, @Y, @Z)
END
SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
COMMIT TRAN
END
If your record does not exist, begin an explicit transaction. Both HOLDLOCK and UPDLOCK are required (thanks @MikaelEriksson). The table hints hold the shared lock and update lock for the duration of the transaction. This properly serializes the INSERT and reliably avoids the race condition.
To verify, uncomment WAITFOR
and run two or more queries at the same time.