数据库,数据库锁,事务,事务隔离级别,详解
不同的数据库本身在对记录进行DML操作时会使用不同的锁机制,以保证数据操作的安全,按锁定对象不同,锁可分为表锁、页锁、行锁,表锁对整个表进行锁定,页锁对一页记录锁定,行锁只锁定特定的记录行。而锁类型分又分为共享锁和独占锁等等。共享锁可阻止独占锁获取,但允许其它共享锁获取,而独占锁可阻止其它独占锁和共享锁的获取。一般情况下update,delete和select for update语句都会隐私采用行独占锁。不同的数据库我们也可以手动指定锁,如select * from table (tablelock)对表查询时加表锁。直接使用这些锁总会给我们带来一些不便,首先是不同数据库,锁代码差异不同,不易移植,其次用不好会造成死锁,一般情况下写sql时我们并不指定锁,此时数据库会自动分析sql,默认选择合适的锁。是不是还其他方式控制并发访问问题?当然!标准SQL规范中定义了4个事务隔离级别,提供了在不同隔离级别下并发访问时的不同处理能力。在介绍4个隔离级别之前先了解下相关概念:
1. 脏读:
事务A读取了事务B未提交的数据,恰巧事务B对刚才的数据进行了回滚,此时事务A仍然以事务B的数据(无效数据)为基础进行操作,这就发生了脏读。
2. 不可以重复读取:
一个事务对同一条数据重复读取两次,得到的记录值不一样。如:事务A第一次读取账户余额是100。当A再进行第二次读取的时候,事务B将账户余额更新成200后并提交了事务,此时A再进行读取时发现账户余额是200。两次读取,两次不同的值,这就是所谓的“不可重复读取”,值得注意的是,“不可重复读”只因期间其他事务对记录值进行了更改,或者对此记录进行了删除。为了防止不可重复读(即可以重复读取),数据库一般使用行级锁,对一条记录行加锁可以避免其他事务对数据的更改或删除。
3. 幻读:
事务A在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据。这种情况一般是事务A进行第二次查询统计的时候,事务B对相关记录进行新增或删除后并提交事务,此时事务A读取发现记录和第一次的不一致,幻读和不可重复读容易混淆,不可重复读强调了因更新或删除同一条记录引起的两次读取差异,而幻读强调是是因新增或删除(因记录数改变)导致的两次查询出现差异。为了防止幻读数据库一般数据库会对表进行加锁,以防止其他事务对表进行记录新增或删除。
4. 第一类更新丢失(回滚导致):
A事务回滚时,把B事务提交的更改给覆盖了,如:
序号 |
事务A |
事务B |
1 |
开始事务 |
|
2 |
|
开始事务 |
3 |
查询账户余额为1000 |
|
4 |
|
查询账户余额为1000 |
5 |
|
将账户余额改为2000 |
6 |
|
提交事务,更改生效 |
7 |
取出500,并将于余额改为900 |
|
8 |
回滚事务 |
|
9 |
回滚后余额仍为1000,事务B的更改被抹除了 |
|
5. 第二类更新丢失(修改导致):
A事务将B事务已提交的数据覆盖,如:
序号 |
事务A |
事务B |
1 |
|
开始事务 |
2 |
开始事务 |
|
3 |
|
查询余额是1000 |
4 |
查询余额是1000 |
|
5 |
|
取出500修改余额为500 |
6 |
|
提交事务,修改生效 |
7 |
汇入1000更改余额为2000 |
|
8 |
提交事务 |
|
9 |
余额为2000,B的更新丢失 |
|
事务隔离级别分为如下4种:
1. 可读未提交(Read Uncommitted):
一个事务在执行过程中可以看到其他事务未提交的新插入或更新的记录,这是最低级别。
2. 读已提交的(Read Committed)
一个事务在执行过程中可以看到其他事务已提交的新增记录或更新的记录。一般采用共享锁或独占锁来解决。如:事务A要读取数据,并成功获取了共享锁,事务B想修改记录并试图获取独占锁失败,必须等待事务A释放锁。同样事务B想要修改记录并成功获取了独占锁,事务A想读取数据试图获取共享锁失败,并等到事务B独占锁释放方可查询数据。因此这两种情况事务A不会读取到其他事务未提交的数据。
3. 可重复读取(Repeatable Read):
与不可重复读对立,也就是事务A不能看到其他事务B对此记录的更新,保证A两次读取操作结果相同,但要注意的是,可重复读并不能阻止幻读,也就是说:此离级别可以读取到其他事务新增的记录。可以通过“共享读锁”和“独占锁”实现。
4. 序列化(Serializable)
以串行方式操作,最高级,绝对保证数据的完整和一致性,但并发性很差。
对于相关级别的并发问题用一张表显示:
隔离级别 |
第一类更新丢失 |
第二类更新丢失 |
脏读 |
不可重复读 |
幻读 |
Read Uncommitted |
不允许 |
允许 |
允许 |
允许 |
允许 |
Read Committed |
不允许 |
允许 |
不允许 |
允许 |
允许 |
Repeatable Read |
不允许 |
不允许 |
不允许 |
不允许 |
允许 |
Serializable |
不允许 |
不允许 |
不允许 |
不允许 |
不允许 |
JDBC对隔离级别的支持
java.sql.Connection提供了与之相对的4个静态常量:
- TRANSACTION_READ_COMMITTED
- TRANSACTION_READ_UNCOMMITTED
- static int TRANSACTION_REPEATABLE_READ
- static int TRANSACTION_SERIALIZABLE
代码:
Connection con = null;
try {
con = DriverManager.getConnection("url");
con.setAutoCommit(false);
//判断是否支持此隔离级别
if (con.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)) {
con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
}
//其余操作......
con.commit();
} catch (Exception e) {
con.rollback();
e.printStackTrace();
} finally {
if (con != null) {
con.close();
}
}