A query executes concurrently to atomically increment some counters with an aggregate numeric value:
WITH data (
id,
delta
) AS (
VALUES(1,1.2),(2,2.0),(2,0.5)
), agg_data AS (
SELECT
id::bigint,
SUM(delta::numeric) AS sum_delta
FROM
data
GROUP BY
id
ORDER BY
id
)
UPDATE
counters
SET
val = counters.val + agg_data.sum_delta
FROM
agg_data
WHERE
counters.id = agg_data.id;
The order of the id
column in the CTE agg_data
is set, but the UPDATE statement does not follow it and is updating rows in counters
in arbitrary order, which causes deadlocks. Unfortunately, ORDER BY is not supported in UPDATE (wondering if there is a reason for that).
While I could add a sub query which would SELECT FOR UPDATE
, it feels like I'm over complicating things:
WITH data (
id,
delta
) AS (
VALUES(1,1.2),(2,2.0),(2,0.5)
), agg_data AS (
SELECT
id::bigint,
SUM(delta::numeric) AS sum_delta
FROM
data
GROUP BY
id
ORDER BY
id
), locked_counters AS ( -- add this part to lock in sequential order
SELECT
FROM
counters
WHERE
id
IN (SELECT id FROM agg_data)
ORDER BY
id
FOR NO KEY UPDATE)
UPDATE
counters
SET
val = counters.val + agg_data.sum_delta
FROM
agg_data
WHERE
counters.id = agg_data.id;
Considering that my counters
table could be very big and this query needs to be fully optimized for efficiency and integrity - is there a better way to go about this?