Spring事务管理


数据库事务四大特性

  • 原子性(Atomicity)

    ​ 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

  • 一致性(Consistency)

      一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

      拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  • 隔离性(Isolation)

      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

      即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

       关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

  • 持久性(Durability)

     持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

      例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

我们重点介绍事务的隔离性

不考虑事务的隔离性会引发的问题

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,如果部考虑事务的隔离性,将会引发下列问题。

  • 脏读(事务没提交,提前读取)

    脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

  • 不可重复读(两次读的不一致)

    是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

  • 幻读

    事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

    也可以这样理解:这是当事务不是独立执行时发生的一种现象,例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

    幻读和不可重复读都是读取了另一条已经提交的事务(这点与脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

  • 丢失更新

    • 回滚丢失

    当2个事务更新相同的数据源,如果第一个事务被提交,而另外一个事务却被撤销,那么会连同第一个事务所做的跟新也被撤销。也就是说第一个事务做的跟新丢失了。(这一点其实是非常疑惑的,回去google下把,在百度里实在找不到答案)

    • 覆盖丢失

    他和不可重复读本质上是同一类并发问题,通常他被看做不可重复读的特例:当2个或这个多个事务查询同样的记录然后各自基于最初的查询结果更新该行时,会造成第二类丢失更新。因为每个事务都不知道不知道其他事务的存在,最后一个事务对记录做的修改将覆盖其他事务对该记录做的已提交的更新。

数据库事务隔离级别

  • Read uncommitted(未授权读取、读未提交)

    如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。可能出现脏读,事务B读取到了事务A未提交的数据。

  • Read committed(授权读取、读提交)

    读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

    该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。(我们JDK中读写锁的设计就是对应这一层把)

  • Repeatable read(可重复读取)

    读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

    避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。

  • Serializable(序列化)

    提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

    序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。

ISOLATION LEVELdirty readsnon-repeatble readsphantom reads
Read UncommittedYYY
READ COMMITTEDNYY
REPEATABLE READNNY
SerializableNNY
总结

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

共享锁:共享锁和共享锁可以共存。
排他锁:排他锁和所有锁都不能共存。

乐观锁和悲观锁不是数据库中真正存在的锁,只是人们在解决更新丢失时的不同的解决方案,体现的是人们看待事务的态度。
悲观锁:认为每一条sql语句都会出现更新丢失的情况
隔离级别不设置为Serializable,防止效率过低。
在查询时手动加上排他锁。
如果数据库中的数据查询比较多而更新比较少的话,悲观锁将会导致效率低下。

乐观锁:认为每一条sql语句都不会出现更新丢失的情况
在表中增加一个version字段,在更新数据库记录是将version加一,从而在修改数据时通过检查版本号是否改变判断出当前更新基于的查询是否已经是过时的版本。
如果数据库中数据的修改比较多,更新失败的次数会比较多,程序需要多次重复执行更新操作

共享锁(S锁)

  • 用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。

更新锁(U锁)

  • 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。

独占锁(X锁,也叫排他锁)

  • 一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享缩。写是独占锁,可以有效的防止“脏读”。

索引

数据库创建索引能够大大提高系统的性能。

  • 通过创建唯一性的索引,可以保证数据库表中每一行数据的唯一性。
  • 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
  • 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
  • 在使用分组和排序子句进行数据检索时,同样可以显著的减少查询中分组和排序的时间。
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

增加索引也有许多不利的方面。

  • 创建索引和维护索引需要消耗时间,这种时间随着数量的增加而增加。
  • 索引需要占物理空间,除了数据表占据数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要额空间就会更大。
  • 当对表中的数据进行增加,删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

应该对如下的列建立索引

  • 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。
  • 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度。
  • 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。
  • 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
  • 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。

有些列不应该创建索引

  • 在查询中很少使用或者作为参考的列不应该创建索引。
  • 对于那些只有很少数据值的列也不应该增加索引(比如性别,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度)。
  • 对于那些定义为text,image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
  • 当修改性能远远大于检索性能时,不应该创建索引,因为修改性能和检索性能是矛盾的。

创建索引的方法:直接创建和间接创建(在表中定义主键约束或者唯一性约束时,同时也创建了索引)。

索引的特征:

  • 唯一性索引保证在索引列中的全部数据是唯一的,不会包含冗余数据。
  • 复合索引就是一个索引创建在两个列或者多个列上。可以减少在一个表中所创建的索引数量。

Spring事务隔离级别

Isolation LevelDescription
ISOLATION_ DEFAULT使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 。至于数据库的默认隔离级别,如果DBA没有设置的话,MySql默认为 REPEATABLE_READ,Oracle和Sql Server默认为 READ_COMMITTED
ISOLATION_ READ_UNCOMMITTED会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
ISOLATION_ READ_COMMITTED会出现不可重复读、幻读问题(锁定正在读取的行)
ISOLATION_ REPEATABLE_READ会出幻读(锁定所读取的所有行)
ISOLATION_ SERIALIZABLE保证所有的情况不会发生(锁表)

我们一般在使用Spring事务配置的时候直接使用默认的ISOLATION_ DEFAULT ,当然在特殊的场景下,我们需要做出相应的调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值