【持久层】数据库事务基础——事务的隔离级别

来源:互联网 发布:c语言 map 编辑:程序博客网 时间:2024/06/04 23:27

内容导读:

    前三节数据库事务、并发带来的风险以及数据库锁都是为了铺垫事务的隔离性。

    事务的隔离性不是无缘无故就存在的,他的存在是为了解决某一类问题,带来某一些操作的便捷;解决的问题是指数据库并发操作中数据一致性保证,带来的便捷是指定义好隔离级别之后,数据库会为操作自动加锁(不同的隔离级别拥有不同的自动锁粒度),而不用每次操作都手动的加锁。

    写着写着觉得没什么可写的,本文已沦为简单的笔记····


一、数据库事务

    将一组数据库操作看作一个具备特殊数据库语义的执行单元,该执行单元具备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(隔离性)

    隔离性描述的是数据库对待并发操作的态度,即在并发操作数据库的环境下,不同事务之间对彼此造成影响的程度。根据用户对数据一致性环境的需求,数据库支持不同的隔离级别。
    千万要注意,在数据库并发环境下讨论隔离性才有意义,本质上数据库的隔离级别就是自动事务的代名词,只是不同的隔离级别,对并发下各个事物的隔离程度不一样(一个事物能不能看到另一个事务的中间状态,以及看到的中间状态的程度,不同的隔离级别不一样)。更多细节,下文详细讨论。
    不同的隔离级别,要从两个方面来看待:
    a. 不同的隔离级别,select时候MVCC的策略不一样;
    b. 不同的隔离级别,update/insert/delete的时候加锁(需要结合定位数据时是否是用索引、使用何种索引)的策略不一样;

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 setmysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;//临时更改数据库隔离级别,仅对本次会话有效Query OK, 0 rows affectedmysql> 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

1 0