Search code examples
sqldelphiconcurrencyfirebirdauto-increment

Concurrent insert of keys into a table


Probably a trivial question, but I want to get the best possible solution.

Problem:

I have two or more workers that insert keys into one or more tables. The problem arises when two or more workers try to insert the same key into one of those key tables at the same time. Typical problem.

  1. Worker A reads the table if a key exists (SELECT). There is no key.
  2. Worker B reads the table if a key exists (SELECT). There is no key.
  3. Worker A inserts the key.
  4. Worker B inserts the key.
  5. Worker A commits.
  6. Worker B commits. Exception is throws as unique constraint is violated

The key tables are simple pairs. First column is autoincrement integer and the second is varchar key.

What is the best solution to such a concurrency problem? I believe it is a common problem. One way for sure is to handle the exceptions thrown, but somehow I don't believe this is the best way to tackle this.

The database I use is Firebird 2.5

EDIT:

Some additional info to make things clear.

  1. Client side synchronization is not a good approach, because the inserts come from different processes (workers). And I could have workers across different machines someday, so even mutexes are a no-go.
  2. The primary key and the first columns of such a table is autoincrement field. No problem there. The varchar field is the problem as it is something that the client inserts.

Typical such table is a table of users. For instance:

1  2056
2  1044
3  1896
4  5966
...

Each worker check if user "xxxx" exists and if not inserts it.

EDIT 2:

Just for the reference if somebody will go the same route. IB/FB return pair of error codes (I am using InterBase Express components). Checking for duplicate value violation look like this:

except
  on E: EIBInterBaseError do
  begin
    if (E.SQLCode = -803) and (E.IBErrorCode = 335544349) then
    begin
      FKeysConnection.IBT.Rollback;
      EnteredKeys := False;
    end;
  end;
end;

Solution

  • With Firebird you can use the following statement:

    UPDATE OR INSERT INTO MY_TABLE (MY_KEY) VALUES (:MY_KEY) MATCHING (MY_KEY) RETURNING MY_ID
    
    • assuming there is a BEFORE INSERT trigger which will generate the MY_ID if a NULL value is being inserted.

    Here is the documentation.

    Update: The above statement will avoid exceptions and cause every statement to succeed. However, in case of many duplicate key values it will also cause many unnecessary updates. This can be avoided by another approach: just handle the unique constraint exception on the client and ignore it. The details depend on which Delphi library you're using to work with Firebird but it should be possible to examine the SQLCode returned by the server and ignore only the specific case of unique constraint violation.