How to set a unique constraint in a multi-tenant database

时间秒杀一切 提交于 2019-12-13 15:29:34

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!