SQL Server 2016 unique identifier and foreign keys

0

I have a simple db table that stores actions taken by the user.

CREATE TABLE [admin].[AuditRecords]
(
        [Id] [bigint] IDENTITY(1,1) NOT NULL,
        [TypeName] [varchar](200) NOT NULL,
        [IssuedBy] [uniqueidentifier] NOT NULL,
        [IssuedOn] [datetimeoffset](0) NOT NULL,
        [Details] [varchar](max) NOT NULL,

        CONSTRAINT [PK_AuditRecords] 
            PRIMARY KEY CLUSTERED ([Id] ASC)
                        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                              IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                              ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [admin].[AuditRecords] WITH CHECK 
    ADD CONSTRAINT [FK_AuditRecords_UserDetails] 
        FOREIGN KEY([IssuedBy])
        REFERENCES [security].[UserDetails] ([Id])
GO

There's a FK from from auditrecord table to the user details table.

CREATE TABLE [security].[UserDetails]
(
        [Id] [uniqueidentifier] NOT NULL,
        [Email] [varchar](256) NOT NULL,
        [FirstName] [varchar](256) NOT NULL,
        [LastName] [varchar](256) NOT NULL,
        [UserStatusId] [int] NOT NULL,
        [PhoneNumber] [varchar](20) NOT NULL,
        [FaxNumber] [varchar](20) NOT NULL,
        [SignatureId] [int] NULL,
        [SignatureFilePath] [varchar](256) NOT NULL,
        [CreatedDate] [datetime] NOT NULL,
        [ModifiedDate] [datetime] NOT NULL,

        CONSTRAINT [PK__UserDeta__3214EC070E7DD00C] 
            PRIMARY KEY CLUSTERED ([Id] ASC)
                        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                              IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                              ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
        CONSTRAINT [UQ_UserDetails_Email] 
            UNIQUE NONCLUSTERED ([Email] ASC)
                        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                              IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                              ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

I have the following SQL statement that I am using to update the database

INSERT INTO admin.AuditRecords (TypeName, IssuedBy, IssuedOn, Details)
VALUES (@TypeName, @IssuedBy, @IssuedOn, @Details)

Here's the Dapper code

var messageDetails = JsonConvert.SerializeObject(data);

var auditRecord = new
                  {
                      TypeName = data.GetType().Name,
                      IssuedBy = _principal.UserId(),
                      IssuedOn = DateTimeOffset.UtcNow,
                      Details = messageDetails
                  };

var tenant = _tenantSettingsFactory.GetCurrentTenant();

using (var sqlConnection = new SqlConnection(tenant.DbConnection))
{
    await sqlConnection.OpenAsync();

    await sqlConnection.ExecuteAsync(AUDIT_INSERT_SQL, auditRecord);//, transaction);
}

_principle.UserId() returns a Guid and the particular user id that exists in both tables.

When I try to run that query I get a timeout if I pass the guid as lower case. If I pass it upper case, the code works. The id is stored in the database as upper case. I've been able to replicate the same issue in SSMS. When id is passed upper cased the record inserts, when lower it times out.

Here is the stack trace.

System.Data.SqlClient.SqlException (0x80131904): Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
 ---> System.ComponentModel.Win32Exception (258): The wait operation timed out.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParserStateObject.CheckThrowSNIException()
   at System.Data.SqlClient.SqlCommand.CheckThrowSNIException()
   at System.Data.SqlClient.SqlCommand.EndExecuteNonQueryInternal(IAsyncResult asyncResult)
   at System.Data.SqlClient.SqlCommand.EndExecuteNonQuery(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at Dapper.SqlMapper.ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, Object param) in /_/Dapper/SqlMapper.Async.cs:line 678
   at Vortext.RCM.Data.SQL.AuditStore.Append(Object data) in C:\dev\vortext-main-new\vortext-web\Vortext.RCM.Data\SQL\AuditStore.cs:line 48
   at Vortext.Common.Domain.Decorators.CommandStoreDecorator`1.HandleAsync(TCommand command)
   at Vortext.RCM.Common.Decorators.TransactionCommandDecorator`1.HandleAsync(TCommand command) in C:\dev\vortext-main-new\vortext-web\Vortext.RCM\Common\Decorators\TransactionCommandDecorator.cs:line 23
   at CallSite.Target(Closure , CallSite , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
   at Vortext.RCM.Api.CompositionRoot.CommandHandlerMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in C:\dev\vortext-main-new\vortext-web\Vortext.RCM.Api\CompositionRoot\CommandHandlerMiddleware.cs:line 52

Any ideas?

--------------UPDATE----------------

It seems that the cause is a LCK_M_S lock. I think because the id that is being written to IssuedBy is the same Id that is being modified on the UserDetails table the first transaction is blocking the second.

sql-server-2016
dapper
asked on Stack Overflow Jul 28, 2020 by Steven • edited Jul 28, 2020 by Steven

1 Answer

0

it turns out that the real issue is that I am trying to run a separate connection/transaction for the audit records than the main transaction that edited the user. Because the first transaction audits the UserDetails table while the second transaction is trying to update AuditRecords where the FK matches the row being edited in UserDetails, the second transaction times out waiting for the first transaction to complete. Removing the FK solves the issue.

answered on Stack Overflow Jul 29, 2020 by Steven

User contributions licensed under CC BY-SA 3.0