1. 取引の4つの特徴
原子性: トランザクションでは、追加、削除、および変更 (DML) のように、すべて成功するか、すべて失敗します。
一貫性: トランザクションが完了すると、すべてのデータが一貫した状態に保たれる必要があります. たとえば、銀行振込では、A が 1000 を B に転送し、A が 1000 を差し引き、B が 1000 を加算して成功または失敗します.
分離: 複数のトランザクションの実行は互いに干渉しません。
永続性: トランザクションがコミットまたはロールバックされると、データベース内のデータへの変更は永続的になります。
2. 同時トランザクションによって引き起こされる問題は何ですか?
典型的なアプリケーションでは、複数のトランザクションが同時に実行され、多くの場合、同じデータを操作してそれぞれのタスクを完了します (複数のユーザーが同じデータを操作します)。並行性は必要ですが、次のような問題が発生する可能性があります。
更新の損失: 2 つのトランザクションが同時に更新され、2 番目のトランザクションのロールバックにより、最初のトランザクションによって更新されたデータが上書きされ、更新が失われます。または、トランザクション 1 が更新操作を実行し、トランザクション 1 が終了する前にトランザクション 2 も更新され、トランザクション 1 の更新結果がトランザクション 2 によって上書きされます。
ダーティー読み取り: トランザクションがデータにアクセスしてデータを変更していて、この変更がデータベースに送信されていない場合、別のトランザクションもデータにアクセスし、このデータを使用します。このデータはコミットされていないデータであるため、別のトランザクションで読み取ったデータは「ダーティ データ」であり、「ダーティ データ」に基づく操作は正しくない可能性があります。
Unrepeatable read (Unrepeatable read) : トランザクション内で同じデータを複数回読み取ることを指します。このトランザクションが終了していない間、別のトランザクションもデータにアクセスします。次に、最初のトランザクションの 2 つの読み取りの間で、最初のトランザクションによって読み取られたデータは、2 番目のトランザクションの変更により異なる場合があります。これは、1 回のトランザクションで 2 回読み取られたデータが異なる場合に発生するため、繰り返し不可の読み取りと呼ばれます。
ファントム読み取り: ファントム読み取りは、繰り返し不可能な読み取りに似ています。これは、1 つのトランザクション (T1) が数行のデータを読み取り、次に別の同時トランザクション (T2) がデータを挿入するときに発生します。後続のクエリでは、最初のトランザクション (T1) で、幻覚が発生したかのように、存在しないレコードがさらにいくつか検出されるため、幻覚読み取りと呼ばれます。
反復不可読み取りとファントム読み取りの違い:
繰り返し不可能な読み取りの焦点は、レコードを複数回読み取り、いくつかの列の値が変更されていることを見つけるなどの変更です. ファントム読み取りの焦点は、追加または削除です. たとえば、レコードを複数回読み取り、そのことを見つけるレコード数が増加または減少しました。
3.トランザクション分離のレベル
この目的のために、データベース トランザクションのさまざまな程度の同時アクセス制御に対してさまざまな種類の "ロック" メカニズムを提供する必要があり、その結果、さまざまなトランザクション分離レベル (分離レベル (低 -> 高)) が生まれます。SQL および SQL2 標準では、次の 4 つの分離レベルが定義されています。
●コミットされていない読み取り
意味説明:同一データの書き込みトランザクションのみを制限し、それ以外の書き込みトランザクションを禁止します。「更新が失われた」を解決します。(トランザクションが書き込まれると、他のトランザクションは書き込み禁止になります)
名前 説明: コミットされていないデータを読み取ることができます
必要なロック: 排他的書き込みロック
●Read Committed
意味の説明:同じデータの書き込みトランザクションのみを制限し、他の読み取りおよび書き込みトランザクションを禁止します。「ダーティ リード」と「ロスト アップデート」を解決します。(あるトランザクションが書き込みを行うと、他のトランザクションは読み書きが禁止されます)
名前の説明: データを後で送信して読み取る必要があります
必要なロック: 排他的書き込みロック、一時的な共有読み取りロック
●リピータブルリード(Repeatable Read)
意味の説明: 同一データ書き込みトランザクションを制限すると、他の読み取りおよび書き込みトランザクションが禁止され、読み取りトランザクションは他の書き込みトランザクションが禁止されます (読み取りは許可されます)。「繰り返し不可能な読み取り」、「失われた更新」、および「ダーティー読み取り」を解決します。(1トランザクションが書き込み中は他のトランザクションの読み書き禁止、1トランザクション読み取り中は他のトランザクションの書き込み禁止)
なお、幻読は解決していません 幻読の解決は、レンジロック(レンジロック)またはテーブルロックを大きくすることです。
名前解説:繰り返し読める
必要なロック: 排他的書き込みロック、共有読み取りロック
●連載可能
意味の説明: すべての読み取りおよび書き込みトランザクションはシリアル化する必要があります。トランザクションのシリアル化された実行が必要であり、トランザクションは次々にしか実行できず、同時に実行することはできません。「行レベルのロック」だけではトランザクションのシリアル化を実現できない場合は、他のメカニズムを使用して、クエリ操作を実行したばかりのトランザクションが新しく挿入されたデータにアクセスできないようにする必要があります。(あるトランザクションが書き込まれると、他のトランザクションは読み書きが禁止され、あるトランザクションが読み込まれると、他のトランザクションは読み書きが禁止されます)
必要なロック: 範囲ロックまたはテーブル ロック
次の表は、さまざまな例外に対する各分離レベルの制御機能を示しています。
|
更新が失われた |
ダーティリード |
繰り返し不可能な読み取り |
ファントムリーディング |
RU (コミットされていない読み取り) |
避ける |
|
|
|
RC (読み取りコミット) |
避ける |
避ける |
|
|
RR (繰り返し読み取り) |
避ける |
避ける |
避ける |
|
S (連載) |
避ける |
避ける |
避ける |
避ける
|
上記の 4 つの分離レベルの中で最も高いのが Serializable レベルで、最も低いのが Read uncommitted レベルです. もちろん、レベルが高いほどデータの完全性は向上しますが、実行効率は低下します. Serializable のようなレベルは、他のスレッドがロックの外でのみ待機できるようにテーブルをロックすることです (Java マルチスレッドのロックに似ています)。そのため、通常選択される分離レベルは実際の状況に基づいている必要があります。
4. トランザクションの伝搬動作
トランザクション伝搬動作とは、トランザクション メソッドが別のトランザクション メソッドによって呼び出されたときにどのように機能するかを指します。
例: 2 つのトランザクション メソッドがあり、1 つはメソッド A、もう 1 つはメソッド B で、メソッド B がメソッド A で呼び出された場合、メソッド B がトランザクションを開始するか、メソッド A のトランザクションで実行されるかは、メソッド B によって決定されます。繁殖行動が制御されます。
Spring では、7 つのトランザクション伝搬動作が定義されています。
コミュニケーション行動 |
意味 |
PROPAGATION_REQUIRED |
現在のメソッドをトランザクションで実行する必要があることを示します。現在のトランザクションが存在する場合、メソッドはトランザクションで実行されます。そうでない場合は、新しいトランザクションが開始されます。 |
PROPAGATION_SUPPORTS |
メソッドがトランザクション コンテキストを必要としないことを示しますが、現在のトランザクションが存在する場合、メソッドはこのトランザクション サマリーで実行されます。 |
PROPAGATION_MANDATORY |
メソッドをトランザクションで実行する必要があることを示します。現在のトランザクションが存在しない場合は、例外がスローされます |
PROPAGATION_REQUIRED_NEW |
現在のメソッドを独自のトランザクションで実行する必要があることを示します。新しいトランザクションが開始されます。現在のトランザクションがある場合、現在のトランザクションはこのメソッドの実行中に中断されます。JTATransactionManager を使用する場合は、TransactionManager にアクセスする必要があります |
PROPAGATION_NOT_SUPPORTED |
現在のメソッドがトランザクションで実行されるべきではないことを示します。現在のトランザクションがある場合、現在のトランザクションはメソッドの操作中に中断されます。JTATransactionManager を使用する場合は、TransactionManager にアクセスする必要があります。 |
PROPAGATION_NEVER |
現在のメソッドがトランザクション コンテキストで実行されるべきではないことを示します。トランザクションが現在実行中の場合は、例外がスローされます。 |
PROPAGATION_NESTED |
トランザクションが既に存在する場合、ネストされたトランザクションでメソッドが実行されることを示します。ネストされたトランザクションは、現在のトランザクションとは無関係にコミットまたはロールバックできます。現在のトランザクションが存在しない場合、PROPAGATION_REQUIRED のように動作します。Note that each vendor's support for this Propagation behavior is different. リソース マネージャーのドキュメントを参照して、ネストされたトランザクションをサポートできるかどうかを確認できます。 |
1、PROPAGATION_REQUIRED
トランザクションがあればトランザクションをサポートし、トランザクションがなければトランザクションを開始します
デモコード:
@Component
public class Transaction {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
jdbcTemplate.update("insert into users value (12,'wangwu','000000')");
methodB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
jdbcTemplate.update("insert into actor value (20,'范闲',now())");
// 此处抛出异常,方法A和方法B的操作都将回滚
int i = 100 / 0;
}
}
コンテキストにトランザクションがないため、メソッド B を個別に呼び出すと、トランザクションが開かれます。
メソッド A が呼び出されると、コンテキストにトランザクションがないため、トランザクションが開かれます. メソッド B が実行されると、メソッド B はトランザクションがあることを検出し、メソッド B は新しいトランザクションを開始しませんが、メソッド内のトランザクションを開始します.実装中のA。
2、PROPAGATION_SUPPORTS
トランザクションがある場合は現在のトランザクションがサポートされ、トランザクションがない場合はトランザクションは実行されません
デモコード:
@Component
public class Transaction {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
jdbcTemplate.update("insert into users value (12,'wangwu','000000')");
methodB();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
jdbcTemplate.update("insert into actor value (20,'范闲',now())");
// 存在事务回滚操作,不存在事务则不回滚
int i = 100 / 0;
}
メソッド B のみを呼び出し、トランザクションなしで実行
メソッドAを呼び出し、メソッドBがメソッドAのトランザクションに参加し、トランザクションの操作
3、PROPAGATION_MANDATORY
トランザクションがある場合はトランザクションの操作、トランザクションがない場合は例外がスローされます
@Component
public class Transaction {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
jdbcTemplate.update("insert into users value (12,'wangwu','000000')");
methodB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
jdbcTemplate.update("insert into actor value (20,'范闲',now())");
}
}
どのトランザクションも例外をスローしないため、メソッド B を直接呼び出します。
伝播「必須」でマークされたトランザクションの既存のトランザクションが見つかりません
メソッドAを呼び出し、メソッドBはメソッドAのトランザクションに追加されます
4、PROPAGATION_REQUIRED_NEW
PROPAGATION_REQUIRES_NEW を使用するには、JtaTransactionManager をトランザクション マネージャーとして使用する必要があります。
トランザクションが開かれ、トランザクションがある場合、既存のトランザクションは中断されます
@Component
public class Transaction {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeTingA();
methodB();
doSomeTingB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
jdbcTemplate.update("insert into actor value (20,'范闲',now())");
}
public void doSomeTingA() {
jdbcTemplate.update("insert into users value (12,'wangwu','000000')");
}
public void doSomeTingB() {
jdbcTemplate.update("insert into actor value (21,'范思辙',now())");
}
}
電話するとき
main(){
methodA();
}
を呼び出すことと同等です
main(){
TransactionManager tm = null;
try{
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback();//回滚第二个事务
} finally {
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
} catch(RunTimeException ex) {
ts1.rollback();//回滚第一个事务
} finally {
//释放资源
}
}
ここで、ts1 を外部トランザクション、ts2 を内部トランザクションと呼びます。上記のコードからわかるように、ts2 と ts1 は 2 つの独立したトランザクションであり、互いに関連していません。ts2 の成功は ts1 には依存しません。methodB を呼び出した後に doSomeThingB で methodA が失敗した場合でも、methodB の結果はコミットされます。methodB 以外のコードによる結果はロールバックされます
5、PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED总是非事务的运行,并且挂起任何存在的事务,使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
6、PROPAGATION_NEVER
总是非事务的运行,如果存在一个活动的事务,则抛出异常
7.PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套事务中,如果没有活动事务,则按照PROPAGATION_REQUIRED属性执行
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
@Component
public class Transaction {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeTingA();
methodB();
doSomeTingB();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
jdbcTemplate.update("insert into actor value (20,'范闲',now())");
}
public void doSomeTingA() {
jdbcTemplate.update("insert into users value (12,'wangwu','000000')");
}
public void doSomeTingB() {
jdbcTemplate.update("insert into actor value (21,'范思辙',now())");
}
}
单独执行方法B,按照PROPAGATION_REQUIRED属性执行
如果调用方法A,相当于如下效果:
main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try{
methodB();
} catch(RuntimeException ex) {
con.rollback(savepoint);
} finally {
//释放资源
}
doSomeThingB();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
//释放资源
}
}
当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.