Sql-server – SQL Insert with soft row limit in stored procedure

concurrencysql serverstored-procedures

I have a table ([Label]) which i would like to enforce a soft limit on the number of rows a company can insert (stored procedure code below)
If this stored procedure is being called frequently (e.g. a few times per second by different users), is it possible that it will insert a row over the limit due to concurrency issue? Should i use LOCK or TRANSACTION?

CREATE PROCEDURE [dbo].[admin_AddLabel]
@companyId INT,
@label NVARCHAR(50)
AS
DECLARE @limit INT = 10
DECLARE @count INT = (SELECT COUNT(1) FROM Label WHERE CompanyId = @companyId)
IF @count < @limit
BEGIN
INSERT INTO Label (Label, CompanyId)
VALUES (
  @label,
  @companyId
)
RETURN 0
END

Best Answer

DECLARE @count INT = (SELECT 1 FROM Label WHERE CompanyId = @companyId)

This makes little sense. It should be SELECT COUNT(...)

is it possible that it will insert a row over the limit due to concurrency issue?

Yes. More than just one row. If N concurrent users will reach the count at the same point and count limit -1, then all N will proceed and insert thus you will exceed by N-1.

Should i use LOCK or TRANSACTION?

It is possible, is very difficult to get right, and will severely impact performance. And will be easily defeated by inserts that do not go through this stored procedure (data always outlives the app), by updates that modify the CompanyId and so on and so forth.

BEGIN TRANSACTION
DECLARE @count INT = (
  SELECT COUNT(*) 
    FROM Label WITH (UPDLOCK)
    WHERE CompanyId = @companyId)
IF @count < @limit
BEGIN
    INSERT INTO Label (Label, CompanyId)
    VALUES (@label, @companyId)
END
COMMIT

I would strongly advise against doing something like this. Revisit the requirements, a 'soft limit' is very hard to get right in a relational model.

I recommend you read also Enforcing Complex Constraints with Indexed Views, maybe you find something to inspire you.