Uma maneira de lidar com deadlocks é ter um mecanismo de repetição que aguarda um intervalo aleatório e tenta executar a transação novamente. O intervalo aleatório é necessário para que as transações em colisão não continuem colidindo umas com as outras, causando o que é chamado de bloqueio ao vivo - algo ainda mais desagradável para depurar. Na verdade, os aplicativos mais complexos precisarão desse mecanismo de repetição mais cedo ou mais tarde quando precisarem lidar com falhas de serialização de transações.
É claro que se você for capaz de determinar a causa do impasse, geralmente é muito melhor eliminá-lo ou irá voltar para te morder. Para quase todos os casos, mesmo quando a condição de deadlock é rara, o pouco de taxa de transferência e sobrecarga de codificação para obter os bloqueios em ordem determinística ou obter bloqueios mais grosseiros vale a pena para evitar o ocasional grande acerto de latência e o súbito cliff de desempenho ao dimensionar a simultaneidade.
Quando você está obtendo consistentemente duas instruções INSERT em deadlock, é mais provável que seja um problema exclusivo de ordem de inserção de índice. Tente, por exemplo, o seguinte em duas janelas de comando psql:
Thread A | Thread B
BEGIN; | BEGIN;
| INSERT uniq=1;
INSERT uniq=2; |
| INSERT uniq=2;
| block waiting for thread A to commit or rollback, to
| see if this is an unique key error.
INSERT uniq=1; |
blocks waiting |
for thread B, |
DEADLOCK |
V
Normalmente, o melhor curso de ação para resolver isso é descobrir os objetos pai que protegem todas essas transações. A maioria dos aplicativos tem uma ou duas entidades primárias, como usuários ou contas, que são boas candidatas para isso. Então tudo que você precisa é que cada transação obtenha os bloqueios na entidade primária que ela toca via SELECT ... FOR UPDATE. Ou se tocar em vários, bloqueie todos eles, mas sempre na mesma ordem (ordenar por chave primária é uma boa escolha).