六.事务

一、事务的概念

  事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功
  例如:A——B转帐,对应于如下两条sql语句
    update from account set money=money+100 where name=‘B‘;
    update from account set money=money-100 where name=‘A‘;

二、MySQL数据库中操作事务命令

  1、编写测试SQL脚本,如下:

/*创建账户表*/
create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
);

/*插入测试数据*/
insert into account(name,money) values(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);

下面我们在MySQL数据库中模拟A——B转帐这个业务场景

2.1、开启事务(start transaction)

  使用"start transaction"开启MySQL数据库的事务,如下所示:

我们首先在数据库中模拟转账失败的场景,首先执行update语句让A用户的money减少100块钱,如下图所示:

然后我们关闭当前操作的dos命令行窗口,这样就导致了刚才执行的update语句的数据库的事务没有被提交,那么我们对A用户的修改就不算是是真正的修改了,下次在查询A用户的money时,依然还是之前的1000,如下图所示:

2.2、提交事务(commit)

  下面我们在数据库模拟A——B转账成功的场景

我们手动提交(commit)数据库事务之后,A——B转账100块钱的这个业务操作算是真正成功了,A账户中少了100,B账户中多了100。

2.3、回滚事务(rollback)

 通过手动回滚事务,让所有的操作都失效,这样数据就会回到最初的初始状态!

三、JDBC中使用事务

  当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列的JDBC控制事务语句

  • Connection.setAutoCommit(false);//开启事务(start transaction)
  • Connection.rollback();//回滚事务(rollback)
  • Connection.commit();//提交事务(commit)

3.1、JDBC使用事务范例

  在JDBC代码中演示银行转帐案例,使如下转帐操作在同一事务中执行

  "update account set money=money-100 where name=‘A‘"

  update account set money=money+100 where name=‘B‘

  代码如下:

package me.tanlei.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.junit.Test;

import me.tanlei.jdbc.jdbcUtils;

//JDBC中使用事务来模拟转账
public class TransactionDemo {
    @SuppressWarnings("resource")
    @Test
    public void testTransaction() {
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet rSet=null;

        try {
            connection=jdbcUtils.getConnection();
            connection.setAutoCommit(false);//通知数据库开启事务
            String sql="update account set money=money-100 where name=‘A‘";
            statement=connection.prepareStatement(sql);
            statement.executeUpdate();
            String sql2 = "update account set money=money+100 where name=‘B‘";
            statement = connection.prepareStatement(sql2);
            statement.executeUpdate();
            connection.commit();//上面的两sql语句成功之后就通知数据库提交事务;
            System.out.println("成功!!!");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            jdbcUtils.release(connection, statement, rSet);
        }

    }
    /**
     * @Method: testTransaction1
     * @Description: 模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
     *
     */
     @SuppressWarnings("resource")
    @Test
     public void testTransaction2(){
         Connection conn = null;
         PreparedStatement st = null;
         ResultSet rs = null;

         try{
             conn = jdbcUtils.getConnection();
             conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
             String sql1 = "update account set money=money-100 where name=‘A‘";
             st = conn.prepareStatement(sql1);
             st.executeUpdate();
             //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
             int x = 1/0;
             String sql2 = "update account set money=money+100 where name=‘B‘";
             st = conn.prepareStatement(sql2);
             st.executeUpdate();
             conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
             System.out.println("成功!!!");
         }catch (Exception e) {
             e.printStackTrace();
         }finally{
             jdbcUtils.release(conn, st, rs);
         }
     }

     /**
     * @Method: testTransaction1
     * @Description: 模拟转账过程中出现异常导致有一部分SQL执行失败时手动通知数据库回滚事务
     *
     */
     @SuppressWarnings("resource")
    @Test
     public void testTransaction3(){
         Connection conn = null;
         PreparedStatement st = null;
         ResultSet rs = null;

         try{
             conn = jdbcUtils.getConnection();
             conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
             String sql1 = "update account set money=money-100 where name=‘A‘";
             st = conn.prepareStatement(sql1);
             st.executeUpdate();
             //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交
             int x = 1/0;
             String sql2 = "update account set money=money+100 where name=‘B‘";
             st = conn.prepareStatement(sql2);
             st.executeUpdate();
             conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
             System.out.println("成功!!!");
         }catch (Exception e) {
             try {
                 //捕获到异常之后手动通知数据库执行回滚事务的操作
                 conn.rollback();
             } catch (SQLException e1) {
                 e1.printStackTrace();
             }
             e.printStackTrace();
         }finally{
             jdbcUtils.release(conn, st, rs);
         }
     }
}

3.2、设置事务回滚点

  在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点

  Savepoint sp = conn.setSavepoint();
  Conn.rollback(sp);
  Conn.commit();//回滚后必须通知数据库提交事务
  设置事务回滚点范例

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

4.1、原子性(Atomicity)

  原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败

4.2、一致性(Consistency)

  官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。

4.3、隔离性(Isolation)

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

4.4、持久性(Durability)

  持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

  事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别

五、事务的隔离级别

  多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

5.1、事务不考虑隔离性可能会引发的问题  

  如果事务不考虑隔离性,可能会引发如下问题:

  1、脏读

     脏读指一个事务读取了另外一个事务未提交的数据

     这是非常危险的,假设A向B转帐100元,对应sql语句如下所示
          1.update account set money=money+100 where name=‘B‘;    
          2.update account set money=money-100  where name=‘A‘;
        当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱。如果A等B走后再回滚,B就会损失100元。  

  2、不可重复读

  不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
  例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户内存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
  不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据
  很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

  3、虚读(幻读)

  虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致
  如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。

5.2、事务隔离性的设置语句

  MySQL数据库共定义了四种隔离级别:

  1. Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
  2. Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
  3. Read committed(读已提交):可避免脏读情况发生。
  4. Read uncommitted(读未提交):最低级别,以上情况均无法保证。

  mysql数据库查询当前事务隔离级别:select @@tx_isolation

  例如:

mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)

  mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名

  例如:

5.3、使用MySQL数据库演示不同隔离级别下的并发问题

  同时打开两个窗口模拟2个用户并发访问数据库

1、当把事务的隔离级别设置为read uncommitted时,会引发脏读、不可重复读和虚读

  A窗口
    set transaction isolation level  read uncommitted;--设置A用户的数据库隔离级别为Read uncommitted(读未提交)
    start transaction;--开启事务
    select * from account;--查询A账户中现有的钱,转到B窗口进行操作
    select * from account--发现a多了100元,这时候A读到了B未提交的数据(脏读)

  B窗口
    start transaction;--开启事务
    update account set money=money+100 where name=‘A‘;--不要提交,转到A窗口查询

2、当把事务的隔离级别设置为read committed时,会引发不可重复读和虚读,但避免了脏读

  A窗口
    set transaction isolation level  read committed;
    start transaction;
    select * from account;--发现a帐户是1000元,转到b窗口
    select * from account;--发现a帐户多了100,这时候,a读到了别的事务提交的数据,两次读取a帐户读到的是不同的结果(不可重复读)
  B窗口
    start transaction;
    update account set money=money+100 where name=‘aaa‘;
    commit;--转到a窗口

3、当把事务的隔离级别设置为repeatable read(mysql默认级别)时,会引发虚读,但避免了脏读、不可重复读

  A窗口
    set transaction isolation level repeatable read;
    start transaction;
    select * from account;--发现表有4个记录,转到b窗口
    select * from account;--可能发现表有5条记录,这时候发生了a读取到另外一个事务插入的数据(虚读)
  B窗口
    start transaction;
    insert into account(name,money) values(‘ggg‘,1000);
    commit;--转到a窗口

4、当把事务的隔离级别设置为Serializable时,会避免所有问题

  A窗口
    set transaction isolation level Serializable;
    start transaction;
    select * from account;--转到b窗口

  B窗口
    start transaction;
    insert into account(name,money) values(‘ggg‘,1000);--发现不能插入,只能等待a结束事务才能插入

原文地址:https://www.cnblogs.com/tanlei-sxs/p/9505388.html

时间: 2024-10-13 22:11:37

六.事务的相关文章

Java事务(一) - 概述

一. 什么是事务: 所谓事务,就是针对数据库的一组操作(多条sql) 位于同一个事务的操作具备同步的特点,也就是要么都成功,要么都失败 二. 事务的作用: 在实际中,我们的很多操作都是需要由多条sql来共同完成的,例如,A账户给B账户转账就会对应两条sql update account set money = money - 100 where name = 'a'; update account set money = money + 100 where name = 'b'; 假设第一条sq

sql基础的基础

一.数据定义语言(DDL) create table alter table drop table create index alter index drop index create view drop view 二.数据操作语言(DML) Insert Update Delete 三.数据查询语言(DQL) Select 四.数据控制语言(DCL) Alter Password Grant Revoke Create Synonym 五.数据管理命令 Start Audit Stop Aud

数据库进阶了解

一.数据库的备份 1.物理备份:直接复制数据库文件 2.逻辑备份:备份所有执行的sql语句 备份语法: mysqldump -h 服务器 -u 用户名 -p密码  数据库名 > 备份文件.sql 关键参数: 1.-B指定多个库,增加建库语句和use 语句,恢复的时候自动建库. 2.-A或者--all-databases 例如:C:\WINDOWS\system32>mysqldump -u root -p密码 -B -A> f:\数据库备份练习\all.sql 恢复语法:mysql -h

第十六章——处理锁、阻塞和死锁(1)——确定长时间运行的事务

原文:第十六章--处理锁.阻塞和死锁(1)--确定长时间运行的事务 前言: 事务是OLTP系统中的主要部分.它管理数据一致性和数据并发问题,当多个资源同时被读取或者修改相同数据时,SQLServer会通过锁定机制来确保数据库中的数据总是处于一个有效状态.在SQLServer中,锁管理器是负责实现这些锁机制.SQLServer对于不同的资源类型提供不同的锁类型,如数据库.文件.对象.表.区.页和键. 当你使用事务时,依然会遇到由事务引起的问题,这些通常是由于锁.阻塞和死锁引起的. 本系列将讲解这三

springCloud分布式事务实战(六)编写第二个微服务

(1)创建工程 (2)添加 jar pom.xml添加:springboot 父, mysql连接,(mybatis, spring-mybatis springboot ,阿里连接池) ,服务中心客户端. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="

关系型数据库(六),数据库事务

目录 1.ACID 2.事务并发引起的问题 3.事务隔离机制 4.为什么不直接设置为串行化事务隔离级别 六.数据库事务 1.ACID (1)原子性(Atomic) 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节.事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样. (2)一致性(Consistency) 一个事务可以封装状态改变(除非它是一个只读的).事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事

Redis系列六 Redis事务

Redis事务 1.介绍 在Redis事务中可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞. 2.事务的作用 一个队列中,一次性.顺序性.排他性的执行一系列命令. 3.事物执行五中情况 case1:正常执行 执行exec全部成功 Case2:放弃事务 执行Discard Case3:全体连坐 在向事物队列中添加命令的时候报错,然后执行Exec会全部失败. Case4:冤头债主 在向事物队列中添加命令的时候没有报错,但在

第六章 springboot + 事务(转载)

本篇博客转发自:http://www.cnblogs.com/java-zhao/p/5350106.html 在实际开发中,其实很少会用到事务,一般情况下事务用的比较多的是在金钱计算方面. mybatis与spring集成后,其事务该怎么做?其实很简单,直接在上一节代码的基础上在相应的方法(通常是service层)上加上@Transactional注解即可. 1.com.xxx.firstboot.exception.UserException 1 package com.xxx.firstb

[数据库事务与锁]详解六: MySQL中的共享锁与排他锁

注明: 本文转载自http://www.hollischuang.com/archives/923 在MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突.行级锁分为共享锁和排他锁两种,本文将详细介绍共享锁及排他锁的概念.使用方式及注意事项等. 共享锁(Share Lock) 共享锁又称读锁,是读取操作创建的锁.其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁. 如果事务T对数