Isolation des transactions

L’isolation des transactions est une fonctionnalité des bases de données paramétrable depuis JDBC, et donc depuis les frameworks de mapping O/R comme Hibernate, Toplink ou JPA.

Le standard SQL définit 4 niveaux d’isolation, permettant de choisir lesquels des 3 risques de lecture (sale, non reproductible ou fantôme) doivent être évités.

Risques encourus

La lecture sale (dirty read) est le risque le plus grave, puisque dans c’est le cas où il est possible de lire des données mises à jour par une transaction concurrente, mais non validées. Si elles n’ont pas encore été validées, cela signifie peut-être que la transaction concurrente est en train de mettre à jour d’autres informations, nécessaire pour atteindre une bonne cohérence. Dirty Read signifie donc incohérence potentielle. De plus, en cas de rollback, ces informations risquent de disparaître dans un délai réduit.

La lecture non reproductible est le risque de lire des données différentes si on exécute deux fois la même requête de sélection dans une même requête. La différence de résultat est due à une transaction concurrente qui aurait modifié des informations sur des lignes sélectionnées, et les aurait validées (commit).

La lecture fantôme est le risque de travailler sur un ensemble qui a évolué (lignes en plus ou en moins), à cause d’une transaction concurrente.

Modes d’isolation

Le mode d’isolation Uncommited Read est le plus laxiste, puisqu’il n’empêche aucun des risques cités ! On l’utilise rarement, à cause du risque d’incohérence sur les données.

Le mode Commited Read est le mode par défaut de la plupart des bases de données. Du fait qu’il nous prémunisse contre le risque de Dirty Read, ce mode est souvent considéré comme un bon compromis pour les performances.

En mode Repeatable Read, on ne peut pas lire des données qui ont été modifiées mais pas encore validées par d’autres transactions, et on ne peut pas modifier les données lues par une transaction active.

Serializable est le mode le plus strict, car il assure une total cohérence des données au sein d’un transaction, et une complète isolation des transactions entre elles. Dans ce mode, deux transactions qui agissent sur les mêmes tables ne peuvent pas être exécutées de façon concurrente. C’est donc aussi un mode très pénalisant pour les performances…​ Il représentait le mode par défaut dans d’anciennes versions de bases ; on parlait aussi de verrouillage au niveau table, par opposition au niveau ligne du Commited Read.

Ces quatre modes sont définis par la norme SQL. Certaines bases peuvent en proposer d’autres qui ne seront pas supportés par JDBC.

Modes et bases de données

  • Uncommited Read : SqlServer

  • Commited Read : Oracle, PostgeSQL, SqlServer ; c’est le mode par défaut pour toutes ces bases.

  • Repeatable Read : SqlServer

  • Serializable : Oracle, PostgeSQL, SqlServer

Isolation avec JDBC

La modification du niveau d’isolation se fait sur l’objet de connexion.

 Class.forName("com.mysql.jdbc.Driver")
 Connection connexion = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "");
 connexion.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
 ...

L’interface java.sql.Connection fournit 5 constantes, pour choisir entre les 4 modes. Il y en a une par mode, plus TRANSACTION_NONE, qui désactive les transactions.

La modification du mode d’isolation se fait généralement au moment de la connexion, ou, en tout cas, lorsqu’aucune transaction n’est en cours pour cette connexion.

Isolation avec JPA

Avec JPA, dans le cas où on n’utilise pas de datasource, le fichier persistence.xml définit les paramètres de connexion à la base de données.

<persistence>
  <persistence-unit name="librairie" transaction-type="RESOURCE_LOCAL">
     ...
    <properties>
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
      <property name="hibernate.connection.url" value="jdbc:mysql://localhost/mabase"/>
      <property name="hibernate.connection.username" value="root"/>
      <property name="hibernate.connection.password" value=""/>

      <property name="hibernate.connection.isolation" value="2"/>
    </properties>
  </persistence-unit>
</persistence>

Pour spécifier correctement le mode d’isolation, on doit connaître les valeurs des constantes JDBC :

  • TRANSACTION_NONE = 0

  • TRANSACTION_READ_COMMITTED = 2

  • TRANSACTION_READ_UNCOMMITTED = 1

  • TRANSACTION_REPEATABLE_READ = 4

  • TRANSACTION_SERIALIZABLE = 8

Dans le cas où on utilise une datasource, c’est dans son paramétrage, au niveau du serveur d’application, que cette information doit être définie.

Isolation avec Hibernate

La configuration d’isolation avec Hibernate se fait dans le fichier hibernate.cfg.xml, si on n’utilise pas de datasource.

<hibernate-configuration>
  <session-factory>
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost/mabase</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.connection.password"></property>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

    <property name="hibernate.connection.isolation">2</property>
     ...
  </session-factory>
</hibernate-configuration>

Les valeurs pour la propriété d’isolation sont les mêmes que pour JPA.

Références

Je trouve que la doc de Microsoft TransactSQL est assez bonne sur le sujet. On peut aussi se référer à la JavaDoc de l’interface Connection.