SQL 之 事务(Transaction)


一、什么是事务?

事务是逻辑上的一组操作,是由一系列对系统中数据进行访问与更新的操作所组成的一个 程序执行逻辑单元(Unit)
要么都执行,要么都不执行
 

一个最经典的例子 ------ 转账:
A用户向B用户转账100元,这里涉及两个数据操作:A用户账户减少100,B用户账户增加100。如果在两个操作之间数据库崩溃了,导致A少了B没增加,这就麻烦了。所以:
事务就是要保证这两个操作要么都完成,要么都不完成。

 

二、事务的四大特性(ACID)

在这里插入图片描述

1. 原子性(Atomicity)

事务是最小的执行单位,不允许分割。事务的原子性确保动作要么都执行,要么都不执行

2. 一致性(Consistency)

执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的。

事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态,因此当数据库只包含成功事务提交的结果时,就能说数据库处于一致性状态。
而如果数据库系统在运行过程中发生故障, 有些事务尚未完成就被迫中断,这些未完成的事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。

3. 隔离性(Isolation)

指 在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰,各并发事务之间数据库是独立的

不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的 各个事务之间不能互相干扰。

4. 持久性(Durability)

在一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

 

三、并发事务带来的问题

当多个事务并发运行,经常会操作相同的数据来完成各自的任务(即多个用户对同一数据进行操作)。如果没有采取必要的隔离机制就会产生并发问题。
 

1. 脏读(Dirty read)

脏读是指 在一个事务处理过程里读取了另一个未提交的事务中的数据

当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问并使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

2. 修改丢失(Lost to modify)

两个事务T1和T2读取同一个数据并修改,T2提交的结果覆盖了T1提交的结果,导致T1的修改被丢失
在一个事务读取一个数据时,另一个事务也访问了该数据,如果在第一个事务中修改了这个数据后,第二个事务也修改了这个数据,那么第一个事务内的修改结果就会丢失,因此称为修改丢失。

例如:
事务T1读取某表中的数据A=20,事务T2也读取A=20,事务T1修改A=A-1,事务T2也修改A=A-1,最终结果A=19,事务T1的修改被丢失。

3. 不可重复读(Unrepeatableread)

在一个事务内,多次读取同一数据,并且在这个事务还没结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

例如:
事务T1在读取某一数据时,事务T2立马修改了该数据并提交给数据库,事务T1再次读取该数据时就得到了不同的结果,发生了不可重复读。

4. 幻读(Phantom read)

幻读又称虚读。
一个事务T1读取了几行数据,接着另一个并发事务T2插入了一些数据,当事务T1再次进行查询时就会发现多了一些原本不存在的记录(由事务T2插入的记录),就好像发生了幻觉一样,所以称为幻读。

不可重复读和脏读、幻读的区别

不可重复读和脏读的区别

脏读是某一事务读取了另一个事务未提交的脏数据;
不可重复读则是读取了前一事务提交的数据。

不可重复读和幻读区别

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
 
不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改;
幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。

 

四、事务的隔离级别

4.1 SQL 标准定义了四个隔离级别

  1. READ-UNCOMMITTED(读取未提交数据)

    最低的隔离级别,允许读取尚未提交的数据变更
    避免了修改丢失,可能会导致脏读、幻读或不可重复读

    如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。
    该隔离级别可以通过“排他写锁”实现。

  2. READ-COMMITTED(读取已提交数据)

    允许读取并发事务已经提交的数据
    可以阻止脏读,但是幻读或不可重复读仍有可能发生

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

  3. REPEATABLE-READ(可重复读)

    对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
    可以阻止脏读和不可重复读,但幻读仍有可能发生

    这种隔离级别可以通过“共享读锁”和“排他写锁”实现。

    MySQL 默认的隔离级别 是 可重复读

  4. SERIALIZABLE(可串行化)

    最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,但是代价最高,性能最低。
    可以防止脏读、不可重复读以及幻读

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

4.2 隔离级别可解决的事务问题

隔离级别修改丢失脏读不可重复读幻读
READ-UNCOMMITTEDxxx
READ-COMMITTEDxx
REPEATABLE-READx
SERIALIZABLE

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

大多数数据库的默认级别就是 读取已提交 Read committed,比如Sql Server , Oracle。
MySQL 默认级别是 可重复读 Repeatable-Read

 
这里需要注意的是

    与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重复读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生 ,这与其他数据库系统(如 SQL Server) 是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重复读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 SERIALIZABLE(可串行化) 隔离级别。
    因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容) ,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重复读) 并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。

4.3 MySQL 之 查看与修改隔离级别

  1. 通过SELECT @@tx_isolation;命令来查看

    mysql> SELECT @@tx_isolation;
    +-----------------+
    | @@tx_isolation  |
    +-----------------+
    | REPEATABLE-READ |
    +-----------------+
    
  2. 设置隔离级别

    set  [glogal | session]  transaction isolation level 隔离级别名称;
    set tx_isolation=’隔离级别名称;set transaction isolation level repeatable read;
    set tx_isolation=repeatable-read

    设置数据库的隔离级别一定要是在开启事务之前!

    使用JDBC对数据库的事务设置隔离级别:

    在调用Connection对象的setAutoCommit(false)方法之前。调用Connection对象的setTransactionIsolation(level)即可设置当前链接的隔离级别,至于参数level,可以使用Connection对象的字段:
    在这里插入图片描述

    代码如下:

    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    
    try{
    	conn = JdbcUtils.getConnection();
    	//设置该链接的隔离级别
    	conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
    	conn.setAutoCommit(false);	//开启事务
    }
    

    隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。

 

五、事务的创建

  • 隐式事务:事务没有明显的开启和结束的标记
  • 显式事务:事务具有明显的开启和结束的标记 前提:必须先设置自动提交功能为禁用
步骤1:开启事务
    set autocommit=0;
    start transaction; 可选的,显式的开启一个事务。
步骤2:编写事务中的sql语句(select insert update delete)
      语句1
      语句2
步骤3:结束事务
    commit; 提交事务,并使已对数据库进行的所有修改称为永久性的。
    rollback; 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

 

六、事务的传播特性

  1. PROPAGATION_REQUIRED:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。

  2. PROPAGATION_REQUIRES_NEW:如果没有,就新建一个事务;如果有,就将当前事务挂起。

  3. PROPAGATION_NESTED:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。

  4. PROPAGATION_SUPPORTS:如果没有,就以非事务方式执行;如果有,就使用当前事务。

  5. PROPAGATION_NOT_SUPPORTED:如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。

  6. PROPAGATION_NEVER:如果没有,就以非事务方式执行;如果有,就抛出异常。

  7. PROPAGATION_MANDATORY:如果没有,就抛出异常;如果有,就使用当前事务。

 

七、数据库的锁机制

7.1 按锁类型划分,可分为共享锁、排他锁

共享锁(Share Locks,也叫读锁、S锁)

多个事务可封锁一个共享页;
任何事务都不能修改该页;
通常是该页读取完毕,S锁立即被释放。
在执行select语句的时候需要给操作对象(表或者一些记录 )加上共享锁,但加锁之前需要检查是否有排他锁,如果没有,则可以加共享锁(一个对象上可以加n个共享锁),否则不行。
共享锁的释放通常在执行完select语句之后,当然也有可能是在事务结束(包括正常结束和异常结束)的时候被释放,主要取决于数据库所设置的事务隔离级别。
 
其他用户可以并发读取数据,但任何事务都不能获取数据上的排他锁,直到已释放所有共享锁。
若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁(Exclusive Lock,也叫写锁、X锁)

仅允许一个事务封锁此页;
其他任何事务必须等到X锁被释放才能对该锁页进行访问;
X锁一直到事务结束才能被释放。
执行insert、update、delete语句的时候需要给操作的对象加排他锁,在加排他锁之前必须确认该对象上没有其他任何锁,一旦加上排他锁之后,就不能再给这个对象加其他任何锁。
排他锁的释放通常是在事务结束的时候(当然也有例外,就是在数据库事务隔离级别被设置成Read Uncommitted(读未提交数据)的时候,这种情况下排他锁会在执行完更新操作之后被释放,而不是在事务结束的时候)。
 
若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。

两者的区别

共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不 能加排他锁。获取共享锁的事务只能读数据,不能修改数据。

排他锁:如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获取排他锁的事务既能读数据,又能修改数据。

7.2 按锁的粒度划分,可分为表级锁、行级锁、页级锁

表级锁

直接锁定整张表,在锁定期间,其他进程无法对该表进行写操作。如果你是写锁,则其他进程读也不允许。
特点是:开销小、加锁快,不会出现死锁。锁定粒度最大,发生锁冲突的概率最高,并发度最低
 
MYISAM存储引擎采用的就是表级锁。

行级锁

仅对指定的记录进行加锁,这样其他进程还是可以对同一个表中的其他记录进行操作。
特点:开销大,加锁慢,会出现死锁。锁定的粒度最小,发生锁冲突的概率最低,并发度也最高
 
InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁。

页级锁

一次锁定相邻的一组记录。开销和加锁时间介于表级锁和行级锁之间;会出现死锁;锁定粒度也介于表级锁和行级锁之间,并发度一般。

7.3 按使用机制划分,可分为乐观锁、悲观锁

悲观锁(Pessimistic Lock)

当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写“PCC”,又名“悲观锁”】
 
悲观锁,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

乐观锁(Optimistic Lock)

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

 


喜欢就一键三连支持下哩!
如果有错误的地方欢迎指出~😙
转载请标明:
https://blog.csdn.net/vihem/article/details/120983965

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值