Tutoriel MySQL en Go [partie 4: transactions]
Photo par Nathan Dumlao on Unsplash
Rappel :
Ce petit tutoriel vous accompagne sur la mise en place d’un serveur MySQL/MariaDB et sa programmation simple en Go. L’idée c’est de présenter ici le minimum pour avoir une application fonctionnelle. Il s’inscrit dans une petite série d’articles que je voulais faire sur les bases de données.
Sommaire
- Installer votre serveur MySQL, cliquez ici.
- Ouvrir une connexion vers votre serveur MySQL, cliquez ici.
- Transfert de données MySQL avec Go, cliquez ici.
- Transactions dans MySQL avec Go, cliquez ici.
Transactions dans MySQL avec Go
Une transaction, en SQL, c'est un ensemble d'opérations qui doivent toutes réussir ou toutes échouer. C'est le fameux principe ACID (Atomicité, Cohérence, Isolation, Durabilité). En pratique, si une requête échoue au milieu, on annule tout ce qui a été fait depuis le début de la transaction.
En Go, on commence une transaction avec la méthode db.Begin() :
tx, err := db.Begin() if err != nil { log.Fatalf("db.Begin failed: %v", err) }
À partir de là, on n'utilise plus db directement, mais tx. Toutes les requêtes passent par la transaction :
_, err = tx.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "transaction-1") if err != nil { tx.Rollback() log.Fatalf("tx.Exec(INSERT 1) failed: %v", err) } _, err = tx.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "transaction-2") if err != nil { tx.Rollback() log.Fatalf("tx.Exec(INSERT 2) failed: %v", err) }
Vous l'avez remarqué : en cas d'erreur, on appelle tx.Rollback(). Cela annule toutes les opérations effectuées depuis le db.Begin(). Les deux INSERT sont annulés, comme s'ils n'avaient jamais eu lieu.
Si tout se passe bien, on valide la transaction avec tx.Commit() :
err = tx.Commit() if err != nil { log.Fatalf("tx.Commit failed: %v", err) }
C'est seulement à ce moment-là que les modifications deviennent visibles pour les autres connexions.
Un pattern plus robuste
En pratique, on utilise souvent un defer pour s'assurer que le Rollback est toujours appelé en cas d'erreur, même si on oublie de le faire explicitement :
tx, err := db.Begin() if err != nil { log.Fatalf("db.Begin failed: %v", err) } defer tx.Rollback() _, err = tx.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "safe-1") if err != nil { log.Fatalf("tx.Exec failed: %v", err) } _, err = tx.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "safe-2") if err != nil { log.Fatalf("tx.Exec failed: %v", err) } err = tx.Commit() if err != nil { log.Fatalf("tx.Commit failed: %v", err) }
Le defer tx.Rollback() est sans effet si tx.Commit() a déjà été appelé. C'est documenté dans le package database/sql : appeler Rollback sur une transaction déjà validée retourne simplement sql.ErrTxDone. Du coup, on peut le mettre en defer sans risque.
Les requêtes SELECT dans une transaction
On peut aussi faire des SELECT dans une transaction. L'intérêt, c'est que les données lues sont cohérentes avec les modifications en cours :
tx, err := db.Begin() if err != nil { log.Fatalf("db.Begin failed: %v", err) } defer tx.Rollback() _, err = tx.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "pending") if err != nil { log.Fatalf("tx.Exec failed: %v", err) } rows, err := tx.Query(`SELECT id, data FROM ` + tableName + ` WHERE data=?`, "pending") if err != nil { log.Fatalf("tx.Query failed: %v", err) } defer rows.Close() for rows.Next() { var id uint64 var data string if err := rows.Scan(&id, &data); err != nil { log.Fatalf("rows.Scan failed: %v", err) } fmt.Printf("found: %d %s\n", id, data) } err = tx.Commit() if err != nil { log.Fatalf("tx.Commit failed: %v", err) }
Ici, le SELECT voit la ligne "pending" qui vient d'être insérée, même si elle n'est pas encore visible par les autres connexions.
Conclusion
Les transactions en Go sont simples à utiliser. Les trois méthodes à retenir :
- db.Begin() pour démarrer une transaction
- tx.Commit() pour valider
- tx.Rollback() pour annuler
Le pattern defer tx.Rollback() juste après le Begin() est une bonne habitude à prendre. Et surtout, n'oubliez pas : une fois dans une transaction, on utilise tx, pas db.
Tutoriel MySQL en Go
- Tutoriel MySQL en Go [partie 1: installation]
- Tutoriel MySQL en Go [partie 2: connexion et configuration]
- Tutoriel MySQL en Go [partie 3: transfert de données]
- Tutoriel MySQL en Go [partie 4: transactions]