Spring @Transactional - 隔离、传播
有人可以解释一下隔离和“隔离”吗?通过实际示例来了解 @Transactional
注释中的传播参数?
基本上什么时候以及为什么我应该选择更改它们的默认值?
Can someone explain the isolation & propagation parameters in the @Transactional
annotation via a real-world example?
Basically when and why I should choose to change their default values?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
好问题,尽管回答起来并不简单。
传播
定义如何传播交易是相互关联的。常见选项:
REQUIRED
:代码将始终在事务中运行。创建一项新事务或重用一项(如果可用)。REQUIRES_NEW
:代码将始终在新事务中运行。如果存在当前事务,则暂停该事务。@Transactional
的默认值是REQUIRED
,这通常是您想要的。隔离
定义数据契约交易之间。
ISOLATION_READ_UNCOMMITTED
:允许脏读。ISOLATION_READ_COMMITTED
:不允许脏读。ISOLATION_REPEATABLE_READ
:如果在同一个事务中读取同一行两次,结果将始终相同。ISOLATION_SERIALIZABLE
:按顺序执行所有事务。在多线程应用程序中,不同级别具有不同的性能特征。我认为如果您理解脏读概念,您将能够选择一个好的选项。
不同数据库之间的默认值可能有所不同。例如,对于 MariaDB 它是
可重复读取
。脏读何时可能发生的示例:
因此,合理的默认值(如果可以声明的话)可以是 ISOLATION_READ_COMMITTED,它只允许您读取其他正在运行的事务已经提交的值,并结合传播级别为
REQUIRED
。然后,如果您的应用程序有其他需求,您可以从那里开始工作。一个实际示例,说明在进入
provideService
例程时始终会创建新事务并在离开时完成:如果我们改为使用
REQUIRED
,则事务 将保持打开状态。另请注意,回滚的结果可能会有所不同,因为多个执行可能会参与同一事务。
我们可以通过测试轻松验证行为,并查看结果如何随传播级别的不同而变化:
传播级别为
REQUIRES_NEW
时:我们期望fooService.provideService()
由于创建了自己的子事务,因此未回滚。必需
:我们希望所有内容都回滚并且后备存储保持不变。Good question, although not a trivial one to answer.
Propagation
Defines how transactions relate to each other. Common options:
REQUIRED
: Code will always run in a transaction. Creates a new transaction or reuses one if available.REQUIRES_NEW
: Code will always run in a new transaction. Suspends the current transaction if one exists.The default value for
@Transactional
isREQUIRED
, and this is often what you want.Isolation
Defines the data contract between transactions.
ISOLATION_READ_UNCOMMITTED
: Allows dirty reads.ISOLATION_READ_COMMITTED
: Does not allow dirty reads.ISOLATION_REPEATABLE_READ
: If a row is read twice in the same transaction, the result will always be the same.ISOLATION_SERIALIZABLE
: Performs all transactions in a sequence.The different levels have different performance characteristics in a multi-threaded application. I think if you understand the dirty reads concept you will be able to select a good option.
Defaults may vary between difference databases. As an example, for MariaDB it is
REPEATABLE READ
.Example of when a dirty read can occur:
So a sane default (if such can be claimed) could be
ISOLATION_READ_COMMITTED
, which only lets you read values which have already been committed by other running transactions, in combination with a propagation level ofREQUIRED
. Then you can work from there if your application has other needs.A practical example of where a new transaction will always be created when entering the
provideService
routine and completed when leaving:Had we instead used
REQUIRED
, the transaction would remain open if the transaction was already open when entering the routine.Note also that the result of a
rollback
could be different as several executions could take part in the same transaction.We can easily verify the behaviour with a test and see how results differ with propagation levels:
With a propagation level of
REQUIRES_NEW
: we would expectfooService.provideService()
was NOT rolled back since it created its own sub-transaction.REQUIRED
: we would expect everything was rolled back and the backing store was unchanged.PROPAGATION_REQUIRED = 0;如果 DataSourceTransactionObject T1 已针对方法 M1 启动。如果需要另一个方法 M2 Transaction 对象,则不会创建新的 Transaction 对象。 M2 使用相同的对象 T1。
PROPAGATION_MANDATORY = 2;方法必须在事务内运行。如果没有
现有事务正在进行中,将抛出异常。
PROPAGATION_REQUIRES_NEW = 3;如果 DataSourceTransactionObject T1 已针对方法 M1 启动并且正在进行中(执行方法 M1)。如果另一个方法 M2 开始执行,则 T1 在方法 M2 的持续时间内暂停,并为 M2 提供新的 DataSourceTransactionObject T2。 M2 在其自己的事务上下文中运行。
PROPAGATION_NOT_SUPPORTED = 4;如果 DataSourceTransactionObject T1 已针对方法 M1 启动。如果同时运行另一个方法M2。那么 M2 不应该在事务上下文中运行。 T1 暂停,直到 M2 完成。
PROPAGATION_NEVER = 5;这些方法都没有在事务上下文中运行。
隔离级别:
它是关于一个事务可能受到其他并发事务的活动影响的程度。它支持一致性,使多个表中的数据保持一致状态。它涉及锁定数据库中的行和/或表。
多笔交易的问题
场景1。如果 T1 事务从表 A1 中读取由另一个并发事务 T2 写入的数据。如果途中T2回滚,则T1获取到的数据是无效的。例如a=2是原始数据。如果T1读取到T2写入的a=1。如果T2回滚,那么a=1将回滚到DB中的a=2。但是,现在T1的a=1,但在DB表中它被更改为a=2。
场景2。如果T1事务从表A1读取数据。如果另一个并发事务(T2)更新表A1上的数据。那么T1读取到的数据为
与表A1不同。因为T2已经更新了表A1上的数据。例如,如果 T1 读取 a=1 并且 T2 更新 a=2。那么a!=b。
场景 3。如果T1事务从表A1中读取一定行数的数据。如果另一个并发事务 (T2) 在表 A1 上插入更多行。这
T1 读取的行数与表 A1 上的行数不同。
场景 1 称为“脏读”。
场景 2 称为“不可重复读”。
场景 3 称为“幻读”。
因此,隔离级别是延伸到可以预防场景1、场景2、场景3。
您可以通过实现锁定来获得完全的隔离级别。这可以防止发生对同一数据的并发读取和写入。但它影响性能。隔离级别取决于应用程序之间需要多少隔离。
ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的更改。它受到场景 1、场景 2、场景 3 的影响。
ISOLATION_READ_COMMITTED:允许从已提交的并发事务中读取。它可能会受到场景2和场景3的影响。因为其他事务可能正在更新数据。
ISOLATION_REPEATABLE_READ:同一字段的多次读取将产生相同的结果,直到它自行更改。它可能会遇到场景 3 的情况。因为其他事务可能正在插入数据。
ISOLATION_SERIALIZABLE:场景 1、场景 2、场景 3 永远不会发生。这是完全的隔离。它涉及完全锁定。由于锁定,它会影响性能。
您可以使用以下方式进行测试:
您可以使用不同的隔离和传播值进行调试并查看结果。
PROPAGATION_REQUIRED = 0; If DataSourceTransactionObject T1 is already started for Method M1. If for another Method M2 Transaction object is required, no new Transaction object is created. Same object T1 is used for M2.
PROPAGATION_MANDATORY = 2; method must run within a transaction. If no
existing transaction is in progress, an exception will be thrown.
PROPAGATION_REQUIRES_NEW = 3; If DataSourceTransactionObject T1 is already started for Method M1 and it is in progress (executing method M1). If another method M2 start executing then T1 is suspended for the duration of method M2 with new DataSourceTransactionObject T2 for M2. M2 run within its own transaction context.
PROPAGATION_NOT_SUPPORTED = 4; If DataSourceTransactionObject T1 is already started for Method M1. If another method M2 is run concurrently. Then M2 should not run within transaction context. T1 is suspended till M2 is finished.
PROPAGATION_NEVER = 5; None of the methods run in transaction context.
An isolation level:
It is about how much a transaction may be impacted by the activities of other concurrent transactions. It a supports consistency leaving the data across many tables in a consistent state. It involves locking rows and/or tables in a database.
The problem with multiple transaction
Scenario 1. If T1 transaction reads data from table A1 that was written by another concurrent transaction T2. If on the way T2 is rollback, the data obtained by T1 is invalid one. E.g. a=2 is original data. If T1 read a=1 that was written by T2. If T2 rollback then a=1 will be rollback to a=2 in DB. But, now, T1 has a=1 but in DB table it is changed to a=2.
Scenario2. If T1 transaction reads data from table A1. If another concurrent transaction (T2) update data on table A1. Then the data that T1 has read is
different from table A1. Because T2 has updated the data on table A1. E.g. if T1 read a=1 and T2 updated a=2. Then a!=b.
Scenario 3. If T1 transaction reads data from table A1 with certain number of rows. If another concurrent transaction (T2) inserts more rows on table A1. The
number of rows read by T1 is different from rows on table A1.
Scenario 1 is called Dirty reads.
Scenario 2 is called Non-repeatable reads.
Scenario 3 is called Phantom reads.
So, isolation level is the extend to which Scenario 1, Scenario 2, Scenario 3 can be prevented.
You can obtain complete isolation level by implementing locking. That is preventing concurrent reads and writes to the same data from occurring. But it affects performance. The level of isolation depends upon application to application how much isolation is required.
ISOLATION_READ_UNCOMMITTED: Allows to read changes that haven’t yet been committed. It suffer from Scenario 1, Scenario 2, Scenario 3.
ISOLATION_READ_COMMITTED: Allows reads from concurrent transactions that have been committed. It may suffer from Scenario 2 and Scenario 3. Because other transactions may be updating the data.
ISOLATION_REPEATABLE_READ: Multiple reads of the same field will yield the same results untill it is changed by itself. It may suffer from Scenario 3. Because other transactions may be inserting the data.
ISOLATION_SERIALIZABLE: Scenario 1, Scenario 2, Scenario 3 never happen. It is complete isolation. It involves full locking. It affects performace because of locking.
You can test using:
You can debug and see the result with different values for isolation and propagation.
其他答案给出了有关每个参数的足够解释;但是,您要求提供一个真实世界的示例,以下示例阐明了不同传播选项的目的:
Suppose you're in charge of implementing a signup service in which a confirmation e-mail is sent to the user. You come up with two service objects, one for enrolling the user and one for sending e-mails, which the latter is called inside the first one. For example something like this:
您可能已经注意到,第二个服务属于传播类型 REQUIRES_NEW,而且它很可能会引发异常(SMTP 服务器关闭、电子邮件无效或其他原因)。您可能不希望整个过程回滚,例如从数据库或其他内容中删除用户信息;因此,您可以在单独的事务中调用第二个服务。
Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:
这意味着每当创建 DAO 对象以及对数据库的潜在访问时,我们都需要确保该调用是从我们的一项服务内部进行的,这意味着应该存在实时事务;否则会出现异常。因此传播的类型是强制。
Enough explanation about each parameter is given by other answers; However, you asked for a real-world example, here is the one that clarifies the purpose of different propagation options:
Suppose you're in charge of implementing a signup service in which a confirmation e-mail is sent to the user. You come up with two service objects, one for enrolling the user and one for sending e-mails, which the latter is called inside the first one. For example something like this:
You may have noticed that the second service is of propagation type REQUIRES_NEW and moreover, chances are it throws an exception (SMTP server down , invalid e-mail, or other reasons). You probably don't want the whole process to roll back, like removing the user information from a database or other things; therefore you call the second service in a separate transaction.
Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:
Meaning that whenever a DAO object, and hence a potential access to DB, is created, we need to reassure that the call was made from inside one of our services, implying that a live transaction should exist; otherwise, an exception occurs. Therefore the propagation is of type MANDATORY.
以下定义来自 byteslounge.com 教程 1 和 2 :
Following definitions are from byteslounge.com tutorials 1 and 2:
事务代表数据库的一个工作单元。
事务传播:Spring 允许您指定事务如何在方法调用和嵌套事务之间传播。
事务隔离:Spring允许您指定事务的隔离级别,这样您就可以确保它们与其他并发事务隔离。
在 spring
TransactionDefinition
接口中,定义了 Spring 兼容的事务属性。@Transactional
注解描述了方法或类的事务属性。锁定感知:隔离级别决定了锁定的持续时间。
读感知:会出现以下3种主要问题:
UPDATES
。INSERTS
和/或DELETES
下面的图表显示了哪种事务隔离级别解决了哪些并发问题:
示例
A Transaction represents a unit of work with a database.
Transaction propagation: Spring allows you to specify how transactions should propagate across method calls and nested transactions.
Transaction isolation: Spring allows you to specify the isolation level of your transactions, so you can ensure that they are isolated from other concurrent transactions.
In spring
TransactionDefinition
interface that defines Spring-compliant transaction properties.@Transactional
annotation describes transaction attributes on a method or class.Locking perception: isolation level determines the duration that locks are held.
Read perception: the following 3 kinds of major problems occurs:
UPDATES
from another tx.INSERTS
and/orDELETES
from another txBelow is a chart which shows which transaction isolation level solves which problems of concurrency:
for examples
您几乎永远不想使用
Read Uncommited
,因为它并不真正符合ACID
。Read Commmited
是一个很好的默认起点。可重复读取
可能只在报告、汇总或聚合场景中需要。请注意,许多数据库(包括 postgres)实际上并不支持可重复读取,您必须使用Serialized
代替。Serialized
对于您知道必须完全独立于其他任何事情发生的事情很有用;可以将其想象为 Java 中的同步
。可序列化与 REQUIRES_NEW 传播密切相关。我将 REQUIRES 用于运行 UPDATE 或 DELETE 查询以及“服务”级别函数的所有函数。对于仅运行 SELECT 的 DAO 级别函数,我使用
SUPPORTS
,如果该函数已经启动(即从服务函数调用),它将参与 TX。You almost never want to use
Read Uncommited
since it's not reallyACID
compliant.Read Commmited
is a good default starting place.Repeatable Read
is probably only needed in reporting, rollup or aggregation scenarios. Note that many DBs, postgres included don't actually support Repeatable Read, you have to useSerializable
instead.Serializable
is useful for things that you know have to happen completely independently of anything else; think of it likesynchronized
in Java. Serializable goes hand in hand withREQUIRES_NEW
propagation.I use
REQUIRES
for all functions that run UPDATE or DELETE queries as well as "service" level functions. For DAO level functions that only run SELECTs, I useSUPPORTS
which will participate in a TX if one is already started (i.e. being called from a service function).事务隔离和事务传播虽然相关,但显然是两个截然不同的概念。在这两种情况下,默认值都是通过使用 声明式事务管理 或 程序化交易管理。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。
事务隔离
对于给定的两个或多个正在运行的事务/与数据库的连接,一个事务中的查询所做的更改如何以及何时对另一事务中的查询产生影响/可见。它还涉及使用哪种数据库记录锁定来将该事务中的更改与其他事务隔离开来,反之亦然。这通常由参与事务的数据库/资源来实现。
。
事务传播
在企业应用程序中针对任何给定的请求/处理过程涉及许多组件来完成工作。其中一些组件标记了将在相应组件及其子组件中使用的事务的边界(开始/结束)。对于组件的事务边界,事务传播指定相应组件是否将参与事务,以及如果调用组件已经或没有已创建/启动的事务,会发生什么情况。这与 Java EE 事务属性相同。这通常由客户端事务/连接管理器实现。
参考:
Spring 事务管理
<一个href="http://en.wikipedia.org/wiki/Isolation_%28database_systems%29">Wiki 事务隔离(数据库系统)
Oracle 的事务隔离级别
Java EE 事务属性(传播)
Spring 框架事务传播
Transaction Isolation and Transaction Propagation although related but are clearly two very different concepts. In both cases defaults are customized at client boundary component either by using Declarative transaction management or Programmatic transaction management. Details of each isolation levels and propagation attributes can be found in reference links below.
Transaction Isolation
For given two or more running transactions/connections to a database, how and when are changes made by queries in one transaction impact/visible to the queries in a different transaction. It also related to what kind of database record locking will be used to isolate changes in this transaction from other transactions and vice versa. This is typically implemented by database/resource that is participating in transaction.
.
Transaction Propagation
In an enterprise application for any given request/processing there are many components that are involved to get the job done. Some of this components mark the boundaries (start/end) of a transaction that will be used in respective component and it's sub components. For this transactional boundary of components, Transaction Propogation specifies if respective component will or will not participate in transaction and what happens if calling component already has or does not have a transaction already created/started. This is same as Java EE Transaction Attributes. This is typically implemented by the client transaction/connection manager.
Reference:
Spring Transaction Management
Wiki Transaction Isolation (database systems)
Oracle on Transaction Isolation Levels
Java EE Transaction Attributes (propagation)
Spring Framework Transaction propagation
我已经使用不同的传播模式运行了
outerMethod
、method_1
和method_2
。以下是不同传播模式的输出。
外部方法
Method_1
Method_2
-- 这里主要外部现有事务用于方法 1 和 2
I have run
outerMethod
,method_1
andmethod_2
with different propagation modes.Below is the output for different propagation modes.
Outer Method
Method_1
Method_2
-- Here Main Outer existing transaction used for both method 1 and 2
我们可以为此添加:
We can add for this:
你可以这样使用:
你也可以使用这个东西:
You can use like this:
You can use this thing also: