Spring Framework Recently it was announced that Support for reactive transaction management . Let's take a closer look at this for R2DBC（SQL Database access response specification ） How it works .
Transaction management is a model , Not technology specific . From this point of view , Its properties and runtime behavior are a function of the implementation technology .
From a database perspective , Imperative and responsive transactions work in the same way . from Java From the perspective of , There are some differences between mandatory and passive transactions .
Let's start with imperative trading .
In imperative transactions , More specifically, aspect oriented transaction management , For example, interceptors , Transaction state is usually transparent to code . The bottom layer is based on API, We can get transaction state and transaction binding resources from somewhere . This place usually exists in ThreadLocal In storage . Imperative transactions assume that all the transactional work of your code is the same Thread.
Another aspect of command transactions is in @Transactional Keep all context data in the method while the transaction is in progress , image JPA Such a tool allows you to go through Java 8 Carry out the results Stream. Streaming , in any case , Streaming requires a closed @Transactional Method . When a transaction is in progress , Data in any transaction does not leave the scope of the method .
I point out these two problems because they behave differently in reactive transactions .
Before proceeding with reactive transactions , We need to improve our understanding of transaction state . Transaction state usually includes transaction state （ Started , Has been submitted , Rolled back ） And the corresponding resource bound to the transaction .
Transactional resources （ For example, database connections ） Usually its transaction process is bound to the underlying transport connection . in the majority of cases , This is a TCP Connect . In the case of a database connection using multiplexing , State is bound to the session object . In rare cases , Database operations accept transaction or session identifiers . therefore , We assume that , We bind the connection to the transaction to include the lowest capability method , Because transaction state usually cannot be migrated across connections .
When using reactive programming , We want to use transactions with the same convenience as traditional applications （ Using the same programming model ）, Again, use annotation based @Transactional The method of transaction annotation of . Back to the idea that transaction management is just a pattern , The only thing we need to change is technology .
The reaction transaction no longer binds its transaction state ThreadLocal, It's bound to the subscriber context . This is the context associated with a particular execution path . Or to put it another way ： Each response sequence implemented gets a subscriber context isolated from other execution . This is already the first difference in mandatory matters .
The second difference is from @Transactional Data escaped from the method . Use Reactive Streams Reactive programming is almost all through the function of reactive operators for data flow and data flow transmission . And asynchronous API comparison , It's also a major advantage ,Publisher The publisher just needs to get the decoding of the database driver , The first element is emitted , Not in Future Wait for the last packet to arrive before completion .
Reactive transactions contain this fact ： Similar to imperative transactions , The transaction starts before it actually works . When we generate data from transactional work , The data flow will flow through the publisher when the transaction is active Publisher. That means the data has escaped @Transactional The way to mark . We will realize that @Transactional Methods are just markers in the reaction sequence , We don't focus on the inside of the method ; We'd rather just look at the impact of subscription and completion .
If any errors occur during the transaction , We may leave behind the data that was processed in the transaction when the transaction was rolled back . This is something your application needs to consider . Reactive transaction management as intended does not delay the launch , Streaming properties are not ignored . Atomic weight ratio flows in applications It's much heavier , So you can handle this in your application .
from Java Point of view , Use R2DBC Reactive database access is completely non blocking . all I / O All use non blocking sockets . So from R2DBC Get is I / O It will no longer block your threads . however , Reactive relational database driver conforms to database communication protocol and database behavior . Although we no longer need to block a thread , But we're still using the database connection , Because this is RDBMS How it works - Send command by command . Some databases allow a little optimization , It's called pipelining . In pipeline mode , The driver keeps sending commands to the connection , Without waiting for the last command to complete .
Usually , You can release the connection if ：
We can still observe the lock that blocks the connection .
The database lock
According to the database you use , You can observe MVCC Behavior or blocking behavior , This is usually a transaction lock . For imperative SQL Database transactions , We usually end up with two （b） lock ：
Our application only works when the database releases the lock . Releasing the lock also unblocks the application thread . Due to non blocking I / O, Using reactive database integration no longer clogs application threads . Database lock behavior still exists . We end up using blocked database connections , But it won't stop two resources （ The other is application threads ）.
from Java From the perspective of ,TCP The connection is cheap .
because SQL How databases work , We still have a strong guarantee of consistency
ACID Standard database
SQL There are three perspectives on database and passive response ：
Is there really any performance advantage ？
You don't choose reactive for throughput , Just for scalability . Some of the impacts will affect throughput based entirely on back pressure . Back pressure is Subscriber The concept of how many items a subscriber can handle at a time .
When the current data is processed , Imperative drivers usually get the next block of data . Plug the driver's underlying connections and threads , Until the database replies （ Imperative acquisition model , The white area between requests is delay ）.
In terms of resource use , Reactive drivers don't block threads . To make a long story short , They have in the process of materialization GC Friendly execution model . During stream generation ,GC Pressure will increase .
You've learned about imperative and reactive database properties . Transaction management needs to be implemented in imperative flow in a different way than reactive code . The changes in the implementation reflect slightly different runtime behavior , Especially when it comes to data escape . By changing performance profiles for latency and resource usage , You can get the same strong consistency guarantee .