Domanda

Ho la seguente tabella dei contatori:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

Vorrei incrementare uno dei contatori, o impostare a zero se la riga corrispondente non esiste ancora. C'è un modo per farlo senza problemi di concorrenza in SQL standard? L'operazione è a volte parte di una transazione, a volte separati.

Lo SQL deve essere eseguito senza modifiche su SQLite, PostgreSQL e MySQL, se possibile.

Una ricerca ha prodotto una serie di idee che o soffrono di problemi di concorrenza, o sono specifici di un database:

  • Prova a INSERT una nuova riga, e UPDATE se c'è stato un errore. Purtroppo, l'errore sul INSERT annulla la transazione corrente.

  • UPDATE la riga, e se sono stati modificati alcuna riga, INSERT una nuova riga.

  • MySQL ha una clausola ON DUPLICATE KEY UPDATE.

EDIT: Grazie per tutte le grandi risposte. Sembra che Paul è giusto, e non c'è un solo modo di fare questo portatile. Questo è abbastanza sorprendente per me, come suona come un'operazione molto semplice.

È stato utile?

Soluzione

MySQL (e successivamente SQLite) supportano anche il REPLACE INTO sintassi:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

Questo identifica automaticamente la chiave primaria e trova una riga corrispondente per modificare, inserendo un nuovo se non viene trovato.

Altri suggerimenti

SQLite supporta sostituzione fila se esiste già:

INSERT OR REPLACE INTO [...blah...]

È possibile ridurre questo per

REPLACE INTO [...blah...]

Questo collegamento è stato aggiunto per essere compatibile con l'espressione REPLACE INTO MySQL.

vorrei fare qualcosa di simile al seguente:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

L'impostazione del valore di generazione a 0 in codice o in SQL, ma l'mediante il tasto ON DUP ... per incrementare il valore. Penso che sia la sintassi in ogni caso.

la clausola UPDATE KEY ON DUPLICATE è la soluzione migliore, perché: SOSTITUIRE fa un DELETE seguita da un INSERT così per un mai così leggero periodo il record viene rimosso creando la mai così vaga possibilità che una query potrebbe tornare dopo aver saltato che se la pagina è stata visualizzata durante la query REPLACE.

Io preferisco INSERT ... ON DUPLICATE UPDATE ... per questo motivo.

La soluzione di jmoz è il migliore: anche se io preferisco la sintassi SET per le parentesi

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;

Non so che si sta andando a trovare una soluzione indipendente dalla piattaforma.

Questo è comunemente chiamato "UPSERT".

Vedere alcune discussioni correlati:

In PostgreSQL non c'è comando merge, e in realtà la scrittura non è banale -. Ci sono in realtà strani casi limite che rendono il compito "interessante"

La migliore (come in: lavorare nelle più possibili condizioni) approccio, è quello di utilizzare la funzione - come quello mostrato in manuale (merge_db).

Se non si desidera utilizzare la funzione, è possibile di solito farla franca:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

Basta ricordare che non è colpa la prova e non riuscire alla fine.

standard SQL fornisce l'istruzione MERGE per questo compito. Non tutti i DBMS supportano l'istruzione MERGE.

Se non si dispone di un modo comune per aggiornare atomicamente o inserire (ad esempio, tramite una transazione) allora si può fallback ad un altro regime di blocco. Un file di 0 byte, il sistema mutex, named pipe, etc ...

Potresti utilizzare un trigger di inserimento? Se fallisce, fare un aggiornamento.

Se sei OK con l'utilizzo di una libreria che scrive il codice SQL per voi, allora si può utilizzare upsert (attualmente Ruby e Python solo):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

che funziona su MySQL, Postgres, e SQLite3.

Si scrive una procedura o definita dall'utente funzione memorizzata (UDF) in MySQL e Postgres. Esso utilizza INSERT OR REPLACE in SQLite3.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top