Implémenter un lock au niveau de la base de données
Salut,
Je travaille sur un programme pouvant tourner sur n machines mais travaillant sur la même base (Disons une MySQL ou une Oracle).
Mais il faut que je sois sûr que certaines opération ne soient réalisées que par un seul programme en même temps.
Donc il me faudrait un genre de lock/mutex/semaphore, bref un truc qui permette de m'assurer qu'un seul de mes programmes fait un truc précis à ce moment là.
Attention, une "infinité" de type d'opération doit être permise, et on ne les connait pas toutes à l'avance.
Il y a aussi une potentielle "infinité" de programme et ils n'ont pas d'identifiant.
De plus il faut absolument éviter les deadlocks en cas de crash de l'application.
Le mécanisme "programme qui tombe" -> "connection qui tombe" -> "lock qui tombe" semble donc être la seule solution. D'où l'utilisation d'un "select for update".
Le niveau d'isolation de transaction est fixé à TRANSACTION_READ_COMMITTED.
Je cherche à pouvoir faire ce genre de code :
Code:
1 2 3 4 5 6 7 8 9 10
| DatabaseLock lock = lockManager.createLock("Operation A");
lockManager.lock(lock);
try
{
// Je suis sûr qu'un seul de mes programme peut être ici à un instant t.
}
finally
{
lockManager.unlock(lock);
} |
Ma solution actuelle ressemble au code suivant (Code à la main histoire d'avoir une vague idée, manque des traitements d'erreurs...), avec une table lock_table disposant d'une colonne name avec une clé primaire dessus :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public class LockManager
{
public DatabaseLock createLock(String name)
{
return new DatabaseLock(name);
}
public void lock(DatabaseLock lock)
{
Connection connection = new Connection();
try
{
connection.execute("insert into lock_table values (" + lock.getName() + ")");*
}
catch (SQLException e)
{
// Si l'exception est une duplication de clé primaire, on ignore.
}
connection.commit();
// A ce stade, devrait y avoir une ligne. On fait un select for update dessus.
connection.execute("select name from lock_table where name = " + lock.getName() + " for update");
lock.setConnection(connection);
}
public void unlock(DatabaseLock lock)
{
Connection connection = lock.getConnection();
connection.execute("delete from lock_table where name = " + lock.getName());
// Libère le lock du select for update et supprime la ligne en même temps.
connection.commit();
connection.close();
}
} |
Dans les faits cela ne fonctionne pas assez bien.
Par exemple, un premier programme peut insérer la ligne et la commiter. Un deuxième peut essayer de faire pareil, et utiliser la ligne du premier pour faire le select for update. Il va finalement supprimer la ligne alors que le premier thread va chercher à faire un select for update dessus.
De plus avoir un select/delete et un insert de la même ligne dans des transactions différentes semble générer des problèmes de deadlock ou la base ne sait plus quelle transaction devrait avoir raison au final...
Bref, c'est loin d'être stable et propre.
Pourtant dans la théorie ce type d'algo (Lock au niveau de la base) semble super pratique et généraliste, mais je ne trouve pas de solution.
Quelqu'un a des idées ?