Ruhezustand und Datenbanksperren
1. Warum Sperren verwenden?
Um zu verstehen, warum der Sperrmechanismus existiert, müssen Sie zunächst das Konzept von Transaktionen verstehen.
Eine Transaktion ist eine Reihe zusammenhängender Vorgänge in der Datenbank, die ACID-Merkmale aufweisen müssen:
A (Atomizität): entweder alle erfolgreich oder alle werden widerrufen.
C (Konsistenz): Um die Konsistenz der Datenbank aufrechtzuerhalten.
I (Isolation): Wenn verschiedene Transaktionen mit denselben Daten arbeiten, müssen sie über einen eigenen Datenraum verfügen.
D (Dauerhaftigkeit): Sobald eine Transaktion erfolgreich beendet wird, müssen die Aktualisierungen, die sie an der Datenbank vornimmt, dauerhaft beibehalten werden.
Unser häufig verwendetes relationales Datenbank-RDBMS implementiert diese Merkmale von Transaktionen. Unter anderem werden Atomizität,
Konsistenz und Haltbarkeit durch Protokolle garantiert. Die Isolation wird durch den
Sperrmechanismus erreicht, auf den wir uns heute konzentrieren. Deshalb brauchen wir den Sperrmechanismus.
Was sind die möglichen Konsequenzen, wenn es keine Sperre und keine Kontrolle über die Isolation gibt?
Update verloren: Die von Transaktion 1 übermittelten Daten wurden von Transaktion 2 überschrieben.
Dirty Read: Transaktion 2 fragt die nicht festgeschriebenen Daten von Transaktion 1 ab.
Falsches Lesen: Transaktion 2 fragt die neuen Daten ab, die von Transaktion 1 übermittelt wurden.
Nicht wiederholbares Lesen: Transaktion 2 fragt die aktualisierten Daten ab, die von Transaktion 1 übermittelt wurden.
Schauen wir uns das Hibernate-Beispiel an. Zwei Threads starten zwei Transaktionen, um dieselbe Datenzeile col_id=1 in der Tabelle tb_account zu bearbeiten.
package com.cdai.orm.hibernate.annotation; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "tb_account") public class Account implements Serializable { private static final long serialVersionUID = 5018821760412231859L; @Id @Column(name = "col_id") private long id; @Column(name = "col_balance") private long balance; public Account() { } public Account(long id, long balance) { this.id = id; this.balance = balance; } public long getId() { return id; } public void setId(long id) { this.id = id; } public long getBalance() { return balance; } public void setBalance(long balance) { this.balance = balance; } @Override public String toString() { return "Account [id=" + id + ", balance=" + balance + "]"; } }
package com.cdai.orm.hibernate.transaction; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.AnnotationConfiguration; import com.cdai.orm.hibernate.annotation.Account; public class DirtyRead { public static void main(String[] args) { final SessionFactory sessionFactory = new AnnotationConfiguration(). addFile("hibernate/hibernate.cfg.xml"). configure(). addPackage("com.cdai.orm.hibernate.annotation"). addAnnotatedClass(Account.class). buildSessionFactory(); Thread t1 = new Thread() { @Override public void run() { Session session1 = sessionFactory.openSession(); Transaction tx1 = null; try { tx1 = session1.beginTransaction(); System.out.println("T1 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session1.get(Account.class, new Long(1)); System.out.println("T1 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() + 100); System.out.println("T1 - Change balance:" + account.getBalance()); tx1.commit(); System.out.println("T1 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx1 != null) tx1.rollback(); } finally { session1.close(); } } }; // 3.Run transaction 2 Thread t2 = new Thread() { @Override public void run() { Session session2 = sessionFactory.openSession(); Transaction tx2 = null; try { tx2 = session2.beginTransaction(); System.out.println("T2 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session2.get(Account.class, new Long(1)); System.out.println("T2 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() - 100); System.out.println("T2 - Change balance:" + account.getBalance()); tx2.commit(); System.out.println("T2 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx2 != null) tx2.rollback(); } finally { session2.close(); } } }; t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) { try { Thread.sleep(2000L); } catch (InterruptedException e) { } } System.out.println("Both T1 and T2 are dead."); sessionFactory.close(); } }
es kann 200 sein, eine Aktualisierung von Transaktion 1 oder 2 kann verloren gehen. Die Protokollausgabe bestätigt dies auch. Die Protokolle der Transaktionen 1 und 2
werden kreuzweise gedruckt.
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=? Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=? T1 - balance=100 T2 - balance=100 T2 - Change balance:0 T1 - Change balance:200 Hibernate: update tb_account set col_balance=? where col_id=? Hibernate: update tb_account set col_balance=? where col_id=? T1 - Commit transaction T2 - Commit transaction Both T1 and T2 are dead.
2. Wie viele Arten von Schlössern gibt es?
weist die Datenbank der Transaktion automatisch eine gemeinsame Sperre zu, um die gelesenen Daten zu sperren.
2. Exklusive Sperre: Wird zum Ändern von Daten verwendet. Andere Transaktionen können sie nicht lesen oder ändern. Die Datenbank wird automatisch zugewiesen, wenn die Transaktion Einfügen,
Aktualisieren und Löschen ausführt.
3. Aktualisierungssperre: Wird verwendet, um Deadlocks zu vermeiden, die durch gemeinsame Sperren während Aktualisierungsvorgängen verursacht werden. Beispielsweise halten die Transaktionen 1 und 2 gleichzeitig gemeinsame Sperren und warten auf den Erhalt exklusiver Sperren. Beim Ausführen der Aktualisierung erhält die Transaktion zunächst die Aktualisierungssperre und aktualisiert dann die Aktualisierungssperre
auf eine exklusive Sperre, wodurch ein Deadlock vermieden wird.
Darüber hinaus können diese Sperren auf verschiedene Objekte in der Datenbank angewendet werden, d. h. diese Sperren können unterschiedliche Granularitäten haben.
Es gibt also viele Arten von Sperren. Es ist zu schwierig, so viele Sperren vollständig zu beherrschen und flexibel zu nutzen.
Sperren hinzu und stuft verschiedene Sperren automatisch hoch und herab. Alles, was wir tun müssen, ist
lernen, die Isolationsstufe entsprechend den unterschiedlichen Geschäftsanforderungen festzulegen.
Im Allgemeinen bietet das Datenbanksystem vier Transaktionsisolationsstufen zur Auswahl:
1. Serialisierbar (serialisierbar): Wenn zwei Transaktionen gleichzeitig dieselben Daten bearbeiten, Transaktion 2 Ich kann nur anhalten und warten.
2. Wiederholbares Lesen: Transaktion 1 kann die neu eingefügten Daten von Transaktion 2 sehen, aber keine Aktualisierungen der
vorhandenen Daten.
3. Read Committed (festgeschriebene Daten lesen): Transaktion 1 kann die neu eingefügten und aktualisierten Daten von Transaktion 2 sehen.
4. Nicht festgeschriebene Daten lesen: Transaktion 1 kann die nicht festgeschriebenen Daten von Transaktion 2 sehen und aktualisieren.
4. Sperren in der Anwendung
Wenn die Datenbank die Isolationsstufe „Read Commited“ annimmt, können in der Anwendung pessimistische Sperren oder optimistische Sperren verwendet werden.
an, um die Datenressourcen zu sperren. Die folgenden Formen werden in MySQL und Oracle unterstützt:
ermöglicht explizit die Verwendung einer exklusiven Sperre zum Sperren der abgefragten Datensätze. Andere Transaktionen müssen die von
select ... for update
Im Ruhezustand können Sie LockMode.UPGRADE beim Laden übergeben, um pessimistische Sperren zu verwenden. Ändern Sie das vorherige Beispiel, indem Sie
einen weiteren LockMode-Parameter beim Get-Methodenaufruf der Transaktionen 1 und 2 übergeben. Wie aus dem Protokoll hervorgeht, laufen die Transaktionen 1 und 2
.
package com.cdai.orm.hibernate.transaction; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import com.cdai.orm.hibernate.annotation.Account; import com.cdai.orm.hibernate.annotation.AnnotationHibernate; public class UpgradeLock { @SuppressWarnings("deprecation") public static void main(String[] args) { final SessionFactory sessionFactory = AnnotationHibernate.createSessionFactory(); // Run transaction 1 Thread t1 = new Thread() { @Override public void run() { Session session1 = sessionFactory.openSession(); Transaction tx1 = null; try { tx1 = session1.beginTransaction(); System.out.println("T1 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session1.get(Account.class, new Long(1), LockMode.UPGRADE); System.out.println("T1 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() + 100); System.out.println("T1 - Change balance:" + account.getBalance()); tx1.commit(); System.out.println("T1 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx1 != null) tx1.rollback(); } finally { session1.close(); } } }; // Run transaction 2 Thread t2 = new Thread() { @Override public void run() { Session session2 = sessionFactory.openSession(); Transaction tx2 = null; try { tx2 = session2.beginTransaction(); System.out.println("T2 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session2.get(Account.class, new Long(1), LockMode.UPGRADE); System.out.println("T2 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() - 100); System.out.println("T2 - Change balance:" + account.getBalance()); tx2.commit(); System.out.println("T2 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx2 != null) tx2.rollback(); } finally { session2.close(); } } }; t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) { try { Thread.sleep(2000L); } catch (InterruptedException e) { } } System.out.println("Both T1 and T2 are dead."); sessionFactory.close(); } }
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=? Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=? T2 - balance=100 T2 - Change balance:0 Hibernate: update tb_account set col_balance=? where col_id=? T2 - Commit transaction T1 - balance=0 T1 - Change balance:100 Hibernate: update tb_account set col_balance=? where col_id=? T1 - Commit transaction Both T1 and T2 are dead.
Zeilensperre und Aktualisierungssperre für die ausgewählte Datenzeile mit col_id 1 hinzufügen.
select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
angewiesen ist die Schleusenarbeiten verwalten. Verwenden Sie die Versionskontrolle in Ihrer Anwendung, um Parallelitätsprobleme zu vermeiden, die
selten auftreten können.
Verwenden Sie im Ruhezustand die Versionsanmerkung, um das Versionsnummernfeld zu definieren.
将DirtyLock中的Account对象替换成AccountVersion,其他代码不变,执行出现异常。
package com.cdai.orm.hibernate.transaction; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Version; @Entity @Table(name = "tb_account_version") public class AccountVersion { @Id @Column(name = "col_id") private long id; @Column(name = "col_balance") private long balance; @Version @Column(name = "col_version") private int version; public AccountVersion() { } public AccountVersion(long id, long balance) { this.id = id; this.balance = balance; } public long getId() { return id; } public void setId(long id) { this.id = id; } public long getBalance() { return balance; } public void setBalance(long balance) { this.balance = balance; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } }
log如下:
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=? Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=? T1 - balance=1000 T2 - balance=1000 T1 - Change balance:900 T2 - Change balance:1100 Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=? Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=? T1 - Commit transaction 2264 [Thread-2] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.cdai.orm.hibernate.transaction.AccountVersion#1] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1934) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2578) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2478) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2805) at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:114) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:180) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:375) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137) at com.cdai.orm.hibernate.transaction.VersionLock$2.run(VersionLock.java:93) Both T1 and T2 are dead.
由于乐观锁完全将事务隔离交给数据库来控制,所以事务1和2交叉运行了,事务1提交
成功并将col_version改为1,然而事务2提交时已经找不到col_version为0的数据了,所以
抛出了异常。
Hibernate查询方法比较
Hibernate主要有三种查询方法:
1.HQL (Hibernate Query Language)
和SQL很类似,支持分页、连接、分组、聚集函数和子查询等特性,
但HQL是面向对象的,而不是面向关系数据库中的表。正因查询语句
是面向Domain对象的,所以使用HQL可以获得跨平台的好处,Hibernate
会自动帮我们根据不同的数据库翻译成不同的SQL语句。这在需要支持
多种数据库或者数据库迁移的应用中是十分方便的。
但得到方便的同时,由于SQL语句是由Hibernate自动生成的,所以这不
利于SQL语句的效率优化和调试,当数据量很大时可能会有效率问题,
出了问题也不便于排查解决。
2.QBC/QBE (Query by Criteria/Example)
QBC/QBE是通过组装查询条件或者模板对象来执行查询的。这在需要
灵活地支持许多查询条件自由组合的应用中是比较方便的。同样的问题
是由于查询语句是自由组装的,创建一条语句的代码可能很长,并且
包含许多分支条件,很不便于优化和调试。
3.SQL
Hibernate也支持直接执行SQL的查询方式。这种方式牺牲了Hibernate跨
数据库的优点,手工地编写底层SQL语句,从而获得最好的执行效率,
相对前两种方法,优化和调试方便了一些。
下面来看一组简单的例子。
package com.cdai.orm.hibernate.query; import java.util.Arrays; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Example; import org.hibernate.criterion.Expression; import com.cdai.orm.hibernate.annotation.Account; public class BasicQuery { public static void main(String[] args) { SessionFactory sessionFactory = new AnnotationConfiguration(). addFile("hibernate/hibernate.cfg.xml"). configure(). addPackage("com.cdai.orm.hibernate.annotation"). addAnnotatedClass(Account.class). buildSessionFactory(); Session session = sessionFactory.openSession(); // 1.HQL Query query = session.createQuery("from Account as a where a.id=:id"); query.setLong("id", 1); List result = query.list(); for (Object row : result) { System.out.println(row); } // 2.QBC Criteria criteria = session.createCriteria(Account.class); criteria.add(Expression.eq("id", new Long(2))); result = criteria.list(); for (Object row : result) { System.out.println(row); } // 3.QBE Account example= new Account(); example.setBalance(100); result = session.createCriteria(Account.class). add(Example.create(example)). list(); for (Object row : result) { System.out.println(row); } // 4.SQL query = session.createSQLQuery( " select top 10 * from tb_account order by col_id desc "); result = query.list(); for (Object row : result) { System.out.println(Arrays.toString((Object[]) row)); } session.close(); } }
Hibernate: select account0_.col_id as col1_0_, account0_.col_balance as col2_0_ from tb_account account0_ where account0_.col_id=? Account [id=1, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where this_.col_id=? Account [id=2, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where (this_.col_balance=?) Account [id=1, balance=100] Account [id=2, balance=100] Hibernate: select top 10 * from tb_account order by col_id desc [2, 100] [1, 100]
从log中可以清楚的看到Hibernate对于生成的SQL语句的控制,具体选择
哪种查询方式就要看具体应用了。
更多Java的Hibernate框架数据库操作中锁的使用和查询类型相关文章请关注PHP中文网!