内容导读:
前三节数据库事务、并发带来的风险以及数据库锁都是为了铺垫事务的隔离性。
事务的隔离性不是无缘无故就存在的,他的存在是为了解决某一类问题,带来某一些操作的便捷;解决的问题是指数据库并发操作中数据一致性保证,带来的便捷是指定义好隔离级别之后,数据库会为操作自动加锁(不同的隔离级别拥有不同的自动锁粒度),而不用每次操作都手动的加锁。
写着写着觉得没什么可写的,本文已沦为简单的笔记····
一、数据库事务
将一组数据库操作看作一个具备特殊数据库语义的执行单元,该执行单元具备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(隔离性)
隔离性描述的是数据库对待并发操作的态度,即在并发操作数据库的环境下,不同事务之间对彼此造成影响的程度。根据用户对数据一致性环境的需求,数据库支持不同的隔离级别。
千万要注意,在数据库并发环境下讨论隔离性才有意义,本质上数据库的隔离级别就是自动事务的代名词。更多细节,下文详细讨论。
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默认支持第三种隔离级别,oracle默认支持第二种隔离级别。
关于隔离级别的一些简单操作。
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; }
附注:
写到最后已经不想写了,后面很多深入的东西略了,sorry
版权声明:本文为博主原创文章,未经博主允许不得转载。