事务的四种特性

本文深入解析数据库事务的概念、特性和管理方式,通过具体示例演示事务如何确保数据操作的完整性和一致性,涵盖原子性、一致性、隔离性和持久性的核心属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是事务:
数据库事务是指作为单个逻辑工作单元执行的一系列操作(SQL语句)。这些操作要么全部执行,要么全部不执行。
为什么需要事务
经典的银行转账行为,A账户转给B账户10元,数据库操作需要两步,第一步A账户减10元,第二步B账户加10元,如果没有事务并且在两步中间发生异常,就会导致A的账户少了10元,但B的账户没有变化,如果不能保证这两步操作统一,银行的转账业务也没法进行展开了。
事务管理是每个数据库(oracle、mysql、db等)都必须实现的。
事务特性(4种):

  1. 原子性 (atomicity):强调事务的不可分割.
  2. 一致性 (consistency):事务的执行的前后数据的完整性保持一致.
  3. 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
  4. 持久性(durability) :事务一旦结束,数据就持久到数据库

事务运行模式(3种)

  1. 自动提交事务:默认事务管理模式。如果一个语句成功地完成,则提交该语句;如果遇到错误,则回滚该语句。
  2. 显式事务:以BEGIN TRANSACTION显式开始,以COMMIT或ROLLBACK显式结束。
  3. 隐性事务:当连接以此模式进行操作时,sql将在提交或回滚当前事务后自动启动新事务。无须描述事务的开始,只需提交或回滚每个事务。它生成连续的事务链。

下面做一个事务的示例:
JDBC用了Druid连接池
1、maven引包

	<!--mysql驱动包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>
		<!--druid连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>

2、数据库设计

CREATE TABLE `account` (
  `userid` varchar(64) NOT NULL,
  `username` varchar(64) NOT NULL,
  `accountbalance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `createtime` datetime DEFAULT NULL,
  `updatetime` datetime DEFAULT NULL,
  PRIMARY KEY (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES ('1001', '张三', '1000.00', '2018-11-09 09:39:52', '2018-11-09 09:39:55');

INSERT INTO `account` VALUES ('1002', '李四', '1000.00', '2018-11-09 09:40:12', '2018-11-09 09:40:14');

完成后的样子

3、建立jdbc.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/zhd?useUnicode=true&characterEncoding=UTF-8&InnoDB=true&useSSL=false
username=root
password=123456

4、建立连接池工具类DBPoolConnection

import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;

public class DBPoolConnection {
	static Logger log = LoggerFactory.getLogger(DBPoolConnection.class);
	private static DBPoolConnection dbPoolConnection = null;
	private static DruidDataSource druidDataSource = null;

	static {
		Properties properties = loadPropertiesFile("jdbc.properties");
		try {
			druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); // DruidDataSrouce工厂模式
		} catch (Exception e) {
			log.error("获取配置失败");
		}
	}

	/**
	 * @param string 配置文件名
	 * @return Properties对象
	 */
	private static Properties loadPropertiesFile(String fullFile) {
		if (null == fullFile || fullFile.equals("")) {
			throw new IllegalArgumentException("Properties file path can not be null" + fullFile);
		}
		InputStream inputStream = null;
		Properties p = null;
		try {
			ClassLoader cl = DBPoolConnection.class.getClassLoader();
			inputStream = cl.getResourceAsStream(fullFile);
			p = new Properties();
			p.load(inputStream);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != inputStream) {
					inputStream.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return p;
	}

	/**
	 * 数据库连接池单例
	 * 
	 * @return
	 */
	public static synchronized DBPoolConnection getInstance() {
		if (null == dbPoolConnection) {
			dbPoolConnection = new DBPoolConnection();
		}
		return dbPoolConnection;
	}

	/**
	 * 返回druid数据库连接
	 * 
	 * @return
	 * @throws SQLException
	 */
	public DruidPooledConnection getConnection() throws SQLException {
		return druidDataSource.getConnection();
	}
}

5、建立JDBCUtil工具类

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 对jdbc的完整封装
 *
 */
public class JDBCUtil {

	public static Logger log = LoggerFactory.getLogger(JDBCUtil.class);

	/**
	 * insert update delete SQL语句的执行的统一方法
	 * 
	 * @param sql SQL语句
	 * @param params 参数数组,若没有参数则为null
	 * @return 受影响的行数
	 */
	public int executeUpdate(String sql, Object... params) {
		// 受影响的行数
		int affectedLine = 0;
		// 创建ResultSetMetaData对象
		Connection conn = null;
		PreparedStatement pst = null;
		try {
			// 获得连接
			conn = DBPoolConnection.getInstance().getConnection();
			// 调用SQL
			pst = conn.prepareStatement(sql);

			// 参数赋值
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pst.setObject(i + 1, params[i]);
				}
			}
			/*
			 * 在此 PreparedStatement 对象中执行 SQL 语句, 该语句必须是一个 SQL 数据操作语言(Data
			 * Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE
			 * 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
			 */
			// 执行
			affectedLine = pst.executeUpdate();

		} catch (SQLException e) {
			System.out.println(e.getMessage());
		} finally {
			// 释放资源
			closeAll(conn, pst, null);
		}
		return affectedLine;
	}

	/**
	 * insert update delete SQL语句的执行的统一方法
	 * 
	 * @param sql SQL语句
	 * @param params 参数数组,若没有参数则为null
	 * @return 受影响的行数
	 */
	public int executeUpdate(Connection conn, String sql, Object... params) {
		// 受影响的行数
		int affectedLine = 0;
		// 创建ResultSetMetaData对象
		PreparedStatement pst = null;
		try {
			// 获得连接
			// 调用SQL
			pst = conn.prepareStatement(sql);

			// 参数赋值
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pst.setObject(i + 1, params[i]);
				}
			}
			/*
			 * 在此 PreparedStatement 对象中执行 SQL 语句, 该语句必须是一个 SQL 数据操作语言(Data
			 * Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE
			 * 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
			 */
			// 执行
			affectedLine = pst.executeUpdate();

		} catch (SQLException e) {
			System.out.println(e.getMessage());
		} finally {
			// 释放资源
			 close(pst);
		}
		return affectedLine;
	}

	/**
	 * 获取结果集,并将结果放在List中
	 * 
	 * @param sql SQL语句 params 参数,没有则为null
	 * @return List 结果集
	 */
	public List<Map<String, Object>> excuteQuery(String sql, Object... params) {
		// 创建ResultSetMetaData对象
		ResultSetMetaData rsmd = null;
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rst = null;
		// 创建List
		List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
		try {
			conn = DBPoolConnection.getInstance().getConnection();
			// 调用SQL
			pst = conn.prepareStatement(sql);
			// 参数赋值
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pst.setObject(i + 1, params[i]);
				}
			}
			// 执行
			rst = pst.executeQuery();
			rsmd = rst.getMetaData();
			// 获得结果集列数
			int columnCount = rsmd.getColumnCount();
			// 将ResultSet的结果保存到List中
			while (rst.next()) {
				Map<String, Object> map = new HashMap<String, Object>();
				for (int i = 1; i <= columnCount; i++) {
					map.put(rsmd.getColumnLabel(i), rst.getObject(i));
				}
				list.add(map);// 每一个map代表一条记录,把所有记录存在list中
			}

		} catch (SQLException e1) {
			System.out.println(e1.getMessage());
		} finally {
			// 关闭所有资源
			closeAll(conn, pst, rst);
		}
		return list;
	}

	/**
	 * 关闭所有资源
	 */
	private void closeAll(Connection conn, PreparedStatement pst, ResultSet rst) {
		// 关闭结果集对象
		if (rst != null) {
			try {
				rst.close();
			} catch (SQLException e) {
				System.out.println(e.getMessage());
			}
		}

		// 关闭PreparedStatement对象
		if (pst != null) {
			try {
				pst.close();
			} catch (SQLException e) {
				System.out.println(e.getMessage());
			}
		}

		// 关闭Connection 对象
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				System.out.println(e.getMessage());
			}
		}
	}

	/**
	 * 关闭所有资源
	 */
	public void close(AutoCloseable autoCloseable) {
		if (autoCloseable != null) {
			try {
				autoCloseable.close();
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
		}
	}

	/**
	 * 通过反射机制查询单条记录
	 * 
	 * @param sql
	 * @param params
	 * @param cls
	 * @return
	 * @throws Exception
	 */
	public <T> T findSimpleRefResult(String sql, Class<T> cls, Object... params) {
		T resultObject = null;
		int index = 1;
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rst = null;
		try {
			conn = DBPoolConnection.getInstance().getConnection();
			pst = conn.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pst.setObject(index++, params[i]);
				}
			}
			rst = pst.executeQuery();
			ResultSetMetaData metaData = rst.getMetaData();
			int cols_len = metaData.getColumnCount();
			while (rst.next()) {
				// 通过反射机制创建一个实例
				resultObject = cls.newInstance();
				for (int i = 0; i < cols_len; i++) {
					String cols_name = metaData.getColumnName(i + 1);
					Object cols_value = rst.getObject(cols_name);
					if (cols_value == null) {
						cols_value = "";
					}
					try {
						Field field = cls.getDeclaredField(cols_name.toLowerCase());
						field.setAccessible(true); // 打开javabean的访问权限
						field.set(resultObject, cols_value);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeAll(conn, pst, rst);
		}
		return resultObject;

	}

	/**
	 * 通过反射机制查询多条记录
	 * 
	 * @param sql
	 * @param params
	 * @param cls
	 * @return
	 * @throws Exception
	 */
	public <T> List<T> findMoreRefResult(String sql, Class<T> cls, Object... params) {
		List<T> list = new ArrayList<T>();
		int index = 1;
		Connection connection = null;
		PreparedStatement pstmt = null;
		ResultSet resultSet = null;
		try {
			connection = DBPoolConnection.getInstance().getConnection();
			pstmt = connection.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(index++, params[i]);
				}
			}
			resultSet = pstmt.executeQuery();
			ResultSetMetaData metaData = resultSet.getMetaData();
			int cols_len = metaData.getColumnCount();
			while (resultSet.next()) {
				// 通过反射机制创建一个实例
				T resultObject = cls.newInstance();
				for (int i = 0; i < cols_len; i++) {
					String cols_name = metaData.getColumnName(i + 1);
					Object cols_value = resultSet.getObject(cols_name);
					if (cols_value == null) {
						cols_value = "";
					}
					try {
						Field field = cls.getDeclaredField(cols_name.toLowerCase());
						field.setAccessible(true); // 打开javabean的访问权限
						field.set(resultObject, cols_value);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				list.add(resultObject);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeAll(connection, pstmt, resultSet);
		}
		return list;
	}

	/**
	 * 通过反射机制查询多条记录
	 * 
	 * @param sql
	 * @param params
	 * @param cls
	 * @return
	 * @throws Exception
	 */
	public List<String> findMoreRefResult(String sql, Object... params) {
		List<String> list = new ArrayList<String>();
		int index = 1;
		Connection connection = null;
		PreparedStatement pstmt = null;
		ResultSet resultSet = null;
		try {
			connection = DBPoolConnection.getInstance().getConnection();
			pstmt = connection.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(index++, params[i]);
				}
			}
			resultSet = pstmt.executeQuery();
			while (resultSet.next()) {
				// 通过反射机制创建一个实例
				String cols_value = resultSet.getString(1);
				list.add(cols_value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeAll(connection, pstmt, resultSet);
		}
		return list;
	}

	/**
	 * SQL 查询将查询结果:一行一列
	 * 
	 * @param sql SQL语句
	 * @param params 参数数组,若没有参数则为null
	 * @return 结果集
	 */
	public Object executeQuerySingle(String sql, Object... params) {
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rst = null;
		Object object = null;
		try {
			// 获得连接
			conn = DBPoolConnection.getInstance().getConnection();
			// 调用SQL
			pst = conn.prepareStatement(sql);
			// 参数赋值
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pst.setObject(i + 1, params[i]);
				}
			}
			// 执行
			rst = pst.executeQuery();
			if (rst.next()) {
				object = rst.getObject(1);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			closeAll(conn, pst, rst);
		}

		return object;
	}

}

5、进行测试:

正常情况下:

public class JDBCTest {
	public static void main(String[] args) {
		JDBCUtil jdbcUtil = new JDBCUtil();
		Connection conn = null;
		try {
			conn = DBPoolConnection.getInstance().getConnection();
			conn.setAutoCommit(false);
			String sql_1 = "update account set accountbalance = accountbalance + ? where userid = ?";
			String sql_2 = "update account set accountbalance = accountbalance - ? where userid = ?";
			int line_1 = jdbcUtil.executeUpdate(conn, sql_1, 10, "1001");
			int line_2 = jdbcUtil.executeUpdate(conn, sql_2, 10, "1002");
			conn.commit();
		} catch (SQLException e) {
			try {
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		} finally {
			jdbcUtil.close(conn);
		}
	}
}

运行前:
在这里插入图片描述
运行后:
在这里插入图片描述

异常情况下:

public class JDBCTest {
	public static void main(String[] args) {
		JDBCUtil jdbcUtil = new JDBCUtil();
		Connection conn = null;
		try {
			conn = DBPoolConnection.getInstance().getConnection();
			conn.setAutoCommit(false);
			String sql_1 = "update account set accountbalance = accountbalance + ? where userid = ?";
			String sql_2 = "update account set accountbalance = accountbalance - ? where userid = ?";
			int line_1 = jdbcUtil.executeUpdate(conn, sql_1, 10, "1001");
			if (line_1 == 1) {
				throw new RuntimeException();
			}
			int line_2 = jdbcUtil.executeUpdate(conn, sql_2, 10, "1002");
			conn.commit();
		} catch (SQLException e) {
			try {
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		} finally {
			jdbcUtil.close(conn);
		}
	}
}

运行前:
在这里插入图片描述

运行后:
在这里插入图片描述

<think>我们正在讨论数据库事务的四大特性ACID。根据引用内容,ACID分别代表原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。下面我将详细解释这四个特性,并参考引用的内容进行说明。 1. **原子性(Atomicity)**:事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。引用[1]提到原子性是基础,意味着事务中的操作要么全部完成,要么全部不完成,不存在部分完成的情况。 2. **一致性(Consistency)**:事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。引用[1]指出一致性是约束条件,即事务必须保证数据库的完整性约束(如主键、外键、唯一性约束等)不被破坏。例如,转账操作中,转账前后两个账户的总金额应该保持不变。 3. **隔离性(Isolation)**:多个事务并发执行时,一个事务的执行不应影响其他事务。引用[2]和[3]都提到隔离性是通过并发控制技术(如锁机制)来实现的。数据库提供了不同的隔离级别(如读未提交、读已提交、可重复读、串行化)来平衡隔离性和并发性能。隔离性可以防止脏读、不可重复读和幻读等问题。 4. **持久性(Durability)**:一旦事务提交,则其所做的修改就会永久保存到数据库中,即使系统发生故障也不会丢失。引用[2]提到持久性是目的,通常通过事务日志和备份机制来实现。 下面用转账的例子来说明ACID: - **原子性**:转账操作包括从A账户扣款和向B账户加款,这两个操作要么都成功,要么都失败。如果中间发生故障,系统会回滚到事务开始前的状态。 - **一致性**:转账前后,A和B账户的总金额保持不变。 - **隔离性**:在转账过程中,其他事务不能看到中间状态(比如A账户已扣款但B账户还未加款的状态)。 - **持久性**:转账成功后,即使系统崩溃,重启后账户的变更依然存在。 注意:隔离性通常通过锁机制来实现,但完全隔离会影响并发性能,因此数据库提供了不同的隔离级别,允许开发人员根据需求在一致性和性能之间做出权衡。 最后,生成相关问题。</think>### 数据库事务四大特性 ACID 详解 #### 一、核心特性解析 | **特性** | **核心定义** | **实现机制** | **示例说明** | |----------------|-----------------------------------------------------------------------------|---------------------------------------------|-----------------------------------------------------------------------------| | **原子性**<br>(Atomicity) | 事务是最小执行单元,操作要么全部成功,要么全部失败回滚[^1] | 事务日志(Undo Log) + 回滚机制 | 转账操作:A账户扣款和B账户加款必须同时成功,若中途失败则回退所有操作 | | **一致性**<br>(Consistency) | 事务执行前后数据库必须保持逻辑完整性[^1] | 约束检查(主键/外键) + 业务规则校验 | 转账后总金额不变:A账户$100→$50, B账户$50→$100,总和$150保持不变 | | **隔离性**<br>(Isolation) | 并发事务相互隔离,避免相互干扰[^2][^3] | 锁机制(MVCC) + 隔离级别控制 | 事务A查询账户时,事务B的转账操作不可见(避免脏读) | | **持久性**<br>(Durability) | 事务提交后数据永久保存,即使系统故障也不丢失[^2] | 预写式日志(WAL) + 持久化存储 | 转账成功提交后,即使数据库崩溃重启,账户余额变更依然有效 | ```mermaid graph LR A[事务开始] --> B[原子性:操作原子执行] B --> C[一致性:数据约束校验] C --> D[隔离性:并发控制] D --> E[持久性:数据落盘] E --> F[事务提交] ``` #### 二、特性关联与实现原理 1. **原子性 ↔ 一致性** - 原子性保证操作全成功/全失败,是**一致性的基础**[^1] - 若原子性破坏:转账操作中途失败导致A扣款但B未加款,违反一致性 2. **隔离性 ↔ 一致性** - 隔离级别影响一致性表现(通过锁机制实现)[^2][^3]: $$ \text{隔离级别} \propto \frac{1}{\text{并发性能}} \quad \text{(反比关系)} $$ - 常见隔离问题: - 脏读:读未提交事务的修改 - 不可重复读:同一事务内两次读取结果不同 - 幻读:范围查询结果集变化 3. **持久性实现** 采用预写日志(WAL)保证数据安全: ```plaintext 事务提交流程: 1. 写redo log到磁盘(持久化) 2. 修改内存数据页 3. 异步刷盘(checkpoint机制) ``` 当系统崩溃时,通过redo log重放恢复数据[^2]。 #### 三、ACID特性实践应用 **银行转账场景实现**: ```sql START TRANSACTION; -- 开启事务 UPDATE accounts SET balance = balance - 100 WHERE id = 'A'; -- 原子性 UPDATE accounts SET balance = balance + 100 WHERE id = 'B'; -- 原子性 -- 一致性检查 IF (SELECT SUM(balance) FROM accounts) != 原总额 THEN ROLLBACK; -- 违反一致性则回滚 END IF; COMMIT; -- 持久化保存(隔离性由数据库锁机制保证) ``` #### 四、特性权衡与优化 1. **隔离级别选择**(从弱到强): | 级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 | |------------------|------|------------|------|-----------------------| | READ UNCOMMITTED | ✓ | ✓ | ✓ | 实时统计(低准确性) | | READ COMMITTED | ✗ | ✓ | ✓ | 多数OLTP系统(默认) | | REPEATABLE READ | ✗ | ✗ | ✓ | MySQL InnoDB默认 | | SERIALIZABLE | ✗ | ✗ | ✗ | 金融核心系统 | 2. **性能优化方向**: - 缩短事务执行时间(减少锁持有时间) - 使用乐观锁(CAS)替代悲观锁 - 读写分离(主库写,从库读) > 💡 **案例**:电商系统在`READ COMMITTED`级别下,通过版本号控制实现库存扣减,平衡一致性与并发性能[^3]。 --- ### 相关问题 1. MySQL的`REPEATABLE READ`如何解决幻读问题? 2. 分布式事务如何实现ACID特性? 3. 数据库回滚操作的底层实现原理是什么? 4. 如何通过事务日志(redo/undo log)保证崩溃恢复? 5. 高并发场景下如何选择合适的事务隔离级别?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值