内容导读:
前三节数据库事务、并发带来的风险以及数据库锁都是为了铺垫事务的隔离性。
事务的隔离性不是无缘无故就存在的,他的存在是为了解决某一类问题,带来某一些操作的便捷;解决的问题是指数据库并发操作中数据一致性保证,带来的便捷是指定义好隔离级别之后,数据库会为操作自动加锁(不同的隔离级别拥有不同的自动锁粒度),而不用每次操作都手动的加锁。
写着写着觉得没什么可写的,本文已沦为简单的笔记····
一、数据库事务
将一组数据库操作看作一个具备特殊数据库语义的执行单元,该执行单元具备ACID的事务属性。在数据库事务的ACID属性中,原子性、隔离性和持久性都是为了保证数据的一致性。ACID的数据库语义如下所示。
1. atomic(原子性)
该执行单元中的所有操作,要么全部执行成功,要么全部不执行。
2. consistency(一致性)
执行单元执行完成前后的状态是一致的。一个转账的例子加以说明。
转账前状态:A账户1000块,B账户1000块,转账前的状态是A+B=2000块;
执行单元操作:A向B转账200块;
转账后状态:A账户800块,B账户1200块,转账后的状态是A+B=2000块;
结论:转账前和转账后的状态一致,都是2000块;并没有发生状态不一致情况导致账户A、账户B或银行中任何一方受损或受益。
3. isolation(隔离性)
隔离性描绘的是数据库对待并发事务的态度,即在并发操作数据的环境下,不同事务之间对彼此造成影响的程度。根据用户对数据一致性的要求,数据库支持不同的隔离级别。
本质上数据库的隔离级别就是自动加锁的代名词,不同的隔离级别,对并发下各个事务的隔离程度不一样(一个事务能不能看到另一个事务的中间状态,以及看到中间状态的程度,不同的隔离级别不一样)。
不同隔离级别,要从两方面来看待:
- 不同的隔离级别,select时走MVCC的策略不一样;
- 不同的隔离级别,update\delete\select for share\select for update的时候加锁的策略不一样;
4. durability(持久性)
持久性描述的是当事务成功提交之后,提交数据会被持久化到数据库中,即使数据库立即崩溃,也能够在重启的时候恢复。持久性基于操作日志,简单来说,就是记录数据库操作语句,一条数据库语句包含了数据库的操作和数据,因此只要日志不受损,即使数据库存储介质损坏,也能够通过日志恢复。
二、并发带来的风险
并发操作数据库会导致一些风险,这些风险可以归类为三类读问题和两类更新问题。
1. 脏读
一个事务中读到另一个事务未提交的update。如下图所示,事务A中读到了事务B中未提交的操作,从而导致出现数据不一致的状况。
2. 不可重复度
一个事务中读到另一个事务提交的update。和脏读的区别是,读到的update事务是否已被提交。
读到另一个事务提交的update在大多数情况下无伤大雅,但是如果是在月底做报表的时候,这种情况就出问题了。你需要统计上一个月的数据,但是在统计过程中不断有update,即便你在准确的时间点开启了事务,但是统计的数据依然不准确。解决的办法是为数据库加锁,但是如果你觉得每次统计都重复相同的动作会很麻烦,可以设置隔离级别,让数据库为你自动加锁。
3. 虚读
一个事务中读到另一个事务提交的insert。和不可重复读的区别是,一个是读到提交的update,一个是读到提交的insert。
借用不可重复读中月底报表的例子,读到提交的update,这意味着数据库中原来就存在该条记录,这是修改了字段;而读到insert,则意味着数据库中原来是不存在这条记录的。虽然在统计的sql中添加了where的时间条件,但是读到存在此前的记录和不存在的记录,显然事务之间的影响程度是不一样的。
4. 第一类丢失更新
一个事务的回滚覆盖了另一个事务提交的update。这类丢失更新对数据库造成的影响是很严重的,除非数据库不支持事务,否则无论哪一种隔离级别都必须防止该风险。
5. 第二类丢失更新
一个事务提交的update覆盖了另一个事务提交的update。
三、数据库锁
锁不外乎独占锁和共享锁,放在数据库环境中有行锁和表锁,再细粒度分下来,行共享锁、行独占锁、表共享锁、表独占锁、表共享行独占锁。数据库锁和程序中的锁作用相似,都是为了解决并发环境中的风险——安全性、活跃性和性能问题。
实际上,数据库在并发环境中出现的问题都可以通过加锁来得到解决,本质上也是这么来干的,但是很多加锁操作都是相似的,没必要一直重复相同的动作,因此出现了事务的隔离级别。
MySQL锁详解:点击打开链接
四、事务隔离级别
1. isolation的意义
要解决数据库并发中出现的问题,和写java代码一样,需要通过锁来获取正确的执行时序,这样一来,每次执行操作前都要先执行sql中关于锁的语句,为了简便操作,于是通过设置数据库隔离级别,让数据库自动加锁。
2. isolation的详解
ANSI/ISO SQL 92标准定义了4个等级的隔离级别,分别是READ_UNCOMMITED、READ_COMMITED、REPETABLE_READ、SERIALIZABLE,四个等级的隔离成都递增,允许的并发程度递减,其中mysql支持这四种隔离级别,默认的隔离级别是RR,oracle只支持RC和Serialzable,默认是RC。
关于隔离级别的一些简单操作。
mysql> SELECT @@tx_isolation;//查看当前事务的隔离级别
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set
mysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;//临时更改数据库隔离级别,仅对本次会话有效
Query OK, 0 rows affected
mysql> SELECT @@tx_isolation;//查看已经被更改的隔离级别
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set
数据库隔离级别对并发问题的解决情况。
五、JDBC对事务的支持
JDBC编程中,所有和事务有关的操作都被封装到了connection对象中(by the way,connection的实现被封装成了虚引用保存在driver的ConcurrentHashMap中),源码节选及注释如下。
/**
* com.mysql.jdbc.ConnectionImpl:mysql对connection接口的实现类
*/
public interface java.sql.Connection extends Wrapper, AutoCloseable {
/**
* A constant indicating that transactions are not supported.
* 没有事务情况的也不存在隔离级别
*/
int TRANSACTION_NONE = 0;
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads can occur.
* This level allows a row changed by one transaction to be read
* by another transaction before any changes in that row have been
* committed (a "dirty read"). If any of the changes are rolled back,
* the second transaction will have retrieved an invalid row.
* 除了第一类丢失更新之外,三类读问题和第二类都是更新都会发生
*/
int TRANSACTION_READ_UNCOMMITTED = 1;
/**
* A constant indicating that
* dirty reads are prevented; non-repeatable reads and phantom
* reads can occur. This level only prohibits a transaction
* from reading a row with uncommitted changes in it.
* 防止脏读和第一类丢失更新
*/
int TRANSACTION_READ_COMMITTED = 2;
/**
* A constant indicating that
* dirty reads and non-repeatable reads are prevented; phantom
* reads can occur. This level prohibits a transaction from
* reading a row with uncommitted changes in it, and it also
* prohibits the situation where one transaction reads a row,
* a second transaction alters the row, and the first transaction
* rereads the row, getting different values the second time
* (a "non-repeatable read").
* 防止脏读、不可重复读和第一类丢失更新、第二类丢失更新,不能防止虚读
*/
int TRANSACTION_REPEATABLE_READ = 4;
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads are prevented.
* This level includes the prohibitions in
* <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the
* situation where one transaction reads all rows that satisfy
* a <code>WHERE</code> condition, a second transaction inserts a row that
* satisfies that <code>WHERE</code> condition, and the first transaction
* rereads for the same condition, retrieving the additional
* "phantom" row in the second read.
* 防止脏读、不可重复读、虚读,第一类、第二类丢失更新,此时数据库相当于串行处理事务,并发程度最低
*/
int TRANSACTION_SERIALIZABLE = 8;
/**
* Are we in autoCommit mode?
* JDBC默认是自动提交事务,每次操作数据库都自动进行事务
*/
private boolean autoCommit = true;
/**
* 连接对象支持的事务操作:
* 关于setAutoCommit,因为JDBC默认是自动提交事务,也就是执行一个SQL语句就会提交一次事务,因此如果要想控制事务的提交,设置autoCommit为false即可;
* 这里设置autoCommit为false,并没有和数据库交互,实际上数据库的隔离级别设置的自动事务并不受影响,这句代码只影响JDBC
*/
void setAutoCommit(boolean autoCommit) throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void setTransactionIsolation(int level) throws SQLException;
int getTransactionIsolation() throws SQLException;
Savepoint setSavepoint() throws SQLException;
Savepoint setSavepoint(String name) throws SQLException;
void rollback(Savepoint savepoint) throws SQLException;
}
附注:
写到最后已经不想写了,有兴趣的同学还可以研究研究隔离级别和MVCC的关系,后面很多深入的东西略了,sorry