MySQL 事务

一、什么是事务

数据库是具有事务性的, 这是数据库不同于其他存储方式的区别之一, 一个事务那可以执行多个操作, 这些操作要么全部执行成功, 要么全部执行失败. 举个例子, 用户a向用户b账号内转账两百元钱, 该转账业务要分为两步: 第一步 a的账号内减200元, 第二步b的账号内加200元, 这两步操作必须都执行成功, 或者都没有执行成功, 否则情理上就讲不通了, 这就是事务性.

事务有两个结果:提交(commit)与回滚(rollback), 如果两步转账都没有错误, 则可以提交,转账结果保存进数据库, 如果其中任何一步出错了,则必须回滚数据库, 不会有任何的改动.

二、事务的ACID特性

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,(1)在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!(2)再比如银行转账,从一个账户扣款,另一个账户收款,两者要么都执行,要么都不执行.
一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability).

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

三、MYSQL 事务处理主要有两种方法

1、用 BEGIN, ROLLBACK, COMMIT来实现
开始事务: BEGIN TRANSACTION
提交事务: COMMIT TRANSACTION
回滚事务: ROLLBACK TRANSACTION

2、直接用 SET 来改变 MySQL 的自动提交模式:

SET AUTOCOMMIT=0 禁止自动提交
SET AUTOCOMMIT=1 开启自动提交

示例
先来看一下传统方式向两个表插入数据存在的问题,现在有两个表分别如下:

表 tbl_user
在这里插入图片描述
表 tbl_address
在这里插入图片描述

现在向两个表插入数据
两个函数insertUserData(),insertAddressData();分别用于向两个表插入数据,sql语句分别为

insert into tbl_user(id,name,password,email)" + "values(10,'Tom','123456','tom@gmail.com')";
insert into tbl_address(id,city,country,user_id)" + "values(3,'shanghai','china','10')";

执行后用户表没有问题,但是地址表会出现错误,这是因为第一个提交后主键为id=10,第二个提交如果是这样的顺序可以,但是如果出现第二条先执行,外键为10,而user表中没有id=10的项,所以出错.导致只有一个能提交,这不是我们想看到的结果,需要进行改进.

普通方法代码:

package com.zmy.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

import com.sun.xml.internal.ws.client.sei.ValueSetter;

public class TransactionTest {
	public static Connection getConnection() {
		Connection conn = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jsp_db", "root", "password");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}

	public static void insertUserData() {
		Connection conn = getConnection();
		try {
			String sql = "insert into tbl_user(id,name,password,email)" + "values(10,'Tom','123456','tom@gmail.com')";
			Statement st = conn.createStatement();
			int count = st.executeUpdate(sql);
			System.out.println("向用户表中插入了 " + count + " 条记录");
			conn.close();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	public static void insertAddressData(){
		Connection conn = getConnection();
		try{
			String sql = "insert into tbl_address(id,city,country,user_id)" + "values(3,'shanghai','china','10')";
			Statement st = conn.createStatement();
			int count = st.executeUpdate(sql);
			System.out.println("向地址表中插入了 " + count +" 条记录");
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}


	public static void main(String[] args) {
		insertUserData();
		insertAddressData();
	}

}

改用事务插入数据
主要是main()方法中不同

package com.zmy.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

import com.sun.xml.internal.ws.client.sei.ValueSetter;

public class TransactionTest {
	public static Connection getConnection() {
		Connection conn = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jsp_db", "root", "password");
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return conn;
	}

	public static void insertUserData(Connection conn) throws SQLException {
		String sql = "insert into tbl_user(id,name,password,email)" + "values(11,'Tom','123456','tom@gmail.com')";
		Statement st = conn.createStatement();
		int count = st.executeUpdate(sql);
		System.out.println("向用户表中插入了 " + count + " 条记录");

	}

	public static void insertAddressData(Connection conn) throws SQLException {
		String sql = "insert into tbl_address(id,city,country,user_id)" + "values(3,'shanghai','china','10')";
		Statement st = conn.createStatement();
		int count = st.executeUpdate(sql);
		System.out.println("向地址表中插入了 " + count + " 条记录");

	}

	public static void main(String[] args) {
		Connection conn = null;
		try{
			conn = getConnection();
			conn.setAutoCommit(false); //禁止自动提交
			
			insertUserData(conn);
			insertAddressData(conn);
			
			conn.commit(); //提交事务
		}catch(SQLException e){
			System.out.println("---------捕获SQL异常");
			e.printStackTrace();
			try{
				conn.rollback(); //回滚事务
				System.out.println("--------事务回滚成功");
			}catch (Exception e2) {
				e2.printStackTrace();
			}
		}finally {
			try{
				if(conn!=null)
					conn.close();
			}catch(Exception e3){
				e3.printStackTrace();
			}
		}
	}

}

此时可以插入数据,与数据表中主键不冲突
这里写图片描述

我们来看一下回滚的情况,将第一个sql语句更改如下

String sql = "insert into tbl_user(id,name,password,email)" + "values(11,'Tom','123456','tom@gmail.com')";

第二个sql语句不改变,此时只能一个可以插入,另一个无法插入

insert into tbl_address(id,city,country,user_id)" + "values(3,'shanghai','china','10')";

用事务处理的程序可以捕捉错误

这里写图片描述

四、事务隔离级别

1.事务的并发读问题

  • 脏读 :读取到另一个事务未提交数据;
  • 不可重复读:两次读取不一致;
  • 幻读(虚读):读到另一事务已提交数据。

2.四大隔离级别

4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。

(1) SERIALIZABLE(串行化)

  • 不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
  • 性能最差;

(2) REPEATABLE READ (可重复读)(MySQL的默认隔离级别,一般不用修改)

  • 防止脏读和不可重复读,不能处理幻读问题;
  • 性能比SERIALIZABLE好

(3) READ COMMITTED (读已提交数据)(Oracle的默认隔离级别)

  • 防止脏读,没有处理不可重复读,也没有处理幻读;
  • 性能比REPEATABLE READ好

(4) READ UNCOMMITTED (读未提交数据)

  • 可能出现任何事务并发问题
  • 性能最好
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值