System: Postgres 13.4 on Azure Database for PostgreSQL flexible server
I set up triggers to aggregate data for different products into one aggregation table each. There is also one trigger that adjusts the aggregation tables whenever a discount is entered in the discounts table. Finally, there is one more trigger per product that transfers the data from the aggregation table to a master table. So the master table should always reflect the latest state and can be queried by users.
Now I am experiencing a deadlock. Error information from NodeJS application.
    error: deadlock detected {
        length: 869,
        severity: 'ERROR',
        code: '40P01',
        detail: 'Process 6477 waits for ShareLock on transaction 9458671; blocked by process 6480.\n' +
        'Process 6480 waits for ShareLock on transaction 9458668; blocked by process 6477.',
        hint: 'See server log for query details.',
        position: undefined,
        internalPosition: undefined,
        internalQuery: undefined,
        where: 'while locking tuple (6427,2) in relation "accounts_master"' [...],
        schema: undefined,
        table: undefined,
        column: undefined,
        dataType: undefined,
        constraint: undefined,
        file: 'deadlock.c',
        line: '1155',
        routine: 'DeadLockReport'
    }
The reported SQL statements in my trigger functions are as follows:
UPDATE accounts_master
SET product1_stock = NEW.user_balance,
product1_orders = COALESCE( NEW.daily_orders, NEW.weekly_orders )
WHERE user = NEW.user
PL/pgSQL function updatefunc_aggregation_product1()
UPDATE product1_aggregation
SET daily_orders = daily_orders * NEW.discounts,
last_update = NEW.timestamp
PL/pgSQL function updatefunc_discounts_product1()
There are two questions:
Why does the first code block have a SHARE lock on the table? The documentation states that an UPDATE command acquires a ROW EXCLUSIVE lock but Postgres reports a SHARE lock.
How can I solve this deadlock? The updatefunc_aggregation_commodity1() only needs to read the data and transfer it to the master table. So my approach would be to explicitly acquire a lower level lock e.g. ROW SHARE that does not conflict with the SHARE lock of updatefunc_discounts_product1().
If someone could give feedback on this approach or even a code sample, I would be very grateful. An improved approach is also welcome.
#Edit1: Adding trigger definitions and a chain of events (which statements fire the triggers, to the best of my knowledge).
Trigger definitions:
updatetrigger_discounts_product1
BEFORE INSERT OR UPDATE 
ON product1_discounts
FOR EACH ROW
EXECUTE FUNCTION updatefunc_discounts_product1();
updatetrigger_aggregation_product1
AFTER INSERT OR UPDATE 
ON product1_aggregation
FOR EACH ROW
EXECUTE FUNCTION updatefunc_aggregation_product1();
updatetrigger_master_update_token
BEFORE UPDATE OF product1_stock, product1_orders, [...]
ON accounts_master
FOR EACH ROW
EXECUTE FUNCTION updatefunc_master_update_cols();
Chain of events:
INSERT OR UPDATE on product1_discounts
-> fires updatetrigger_discounts_product1
-> executes updatefunc_discounts_product1()
    UPDATE on product1_aggregation
    -> fires updatetrigger_aggregation_product1
    -> executes updatefunc_aggregation_product1()
        
        UPDATE on accounts_master
        -> fires updatetrigger_master_update
        -> executes updatefunc_master_update_cols()
        
            UPDATE on accounts_master
            [does not fire another trigger because updatetrigger_master_update only fires on specific columns]