问题
This is a muti-tenant app. All records have a client id to separate client data. Customers can insert their own data in this table and set their own unique constraints. Each customer can set a unique constraint on any of the 15 fields or none. So, setting a unique constraint on the actual table will not work.
Currently, to check if a record should be inserted or not we query the database to see if the record exists. If it does we dont insert otherwise we make the insert. If a duplicate record is inserted between the check and the insert then duplicates will leak into the database. Is there a way to guarantee duplicates don't get inserted?
回答1:
As mentioned in the comments, one way to avoid duplicates being inserted because of timing issues between processes running in parallel would be to combine the test for whether a row exists with the INSERT
statement using a WHERE
clause.
I suggested that dynamic SQL is one possible solution, but here's an alternative method using bitmasks which might work for you if the clients' constraint settings are stored in the database. I've made some assumptions, so this might not help you.
Note that this code is simplified to work with only three columns (rather than fifteen as mentioned in the OP). It would probably be best to wrap the logic in a stored procedure if you decide to productionise it.
-- run this code for different values of @ClientId and @DataN to test the behaviour
DECLARE
@ClientId int = 103,
@Data1 int = 1,
@Data2 int = 2,
@Data3 int = 3
DECLARE @clientConstraint TABLE (ClientId int PRIMARY KEY, Data1 bit, Data2 bit, Data3 bit)
DECLARE @clientData TABLE (Id int IDENTITY PRIMARY KEY, ClientId int, Data1 int, Data2 int, Data3 int)
-- set up four clients with different constraints for testing purposes
INSERT @clientConstraint (ClientId, Data1, Data2, Data3)
VALUES
(100,0,0,0),
(101,1,0,0),
(102,0,1,0),
(103,1,0,1)
-- set up an existing row in the data table for each client
INSERT @clientData (ClientId, Data1, Data2, Data3)
VALUES
(100,1,2,3),
(101,1,2,3),
(102,1,2,3),
(103,1,2,3)
-- build a bitmask of the client's unique columns
DECLARE @ClientConstraintMask bigint = 0
SELECT @ClientConstraintMask = Data1 + (Data2 * 2) + (Data3 * 4)
FROM @clientConstraint
WHERE ClientId = @ClientId
-- insert the data, building a uniqueness bitmask and comparing to client's settings
INSERT @clientData (ClientId, Data1, Data2, Data3)
SELECT @ClientId,@Data1, @Data2, @Data3
WHERE ( SELECT
CASE WHEN c1.Data1 = @Data1
THEN @ClientConstraintMask & 1
ELSE 0
END +
CASE WHEN c1.Data2 = @Data2
THEN @ClientConstraintMask & 2
ELSE 0
END +
CASE WHEN c1.Data3 = @Data3
THEN @ClientConstraintMask & 4
ELSE 0
END
FROM @clientData AS c1
WHERE c1.ClientId = @ClientId
) <> @ClientConstraintMask
-- view the results
SELECT * FROM @clientData
It's probably also worth mentioning that, depending on the volume of client data, you might struggle to effectively index the client data table to keep inserts performing well. Consider indexing on the most commonly used unique sets of columns if an index on ClientId
alone doesn't perform well enough.
来源:https://stackoverflow.com/questions/40836035/how-to-set-a-unique-constraint-in-a-multi-tenant-database