数据库事务与四种事务隔离级别详解及对比
事务的四大特性:ACID
原子性(Atomicity):事务中的操作要么都发生,要么都不发生
一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态
隔离性(Isolation):一个事务内部的操作及使用的数据对并发的其他事务是隔离的,不同的事务之间彼此没有任何干扰。
事务隔离又分为不同级别,包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
持久性(Durability):事务对数据库的所有更新是永久保存到数据库,即使系统故障也不会丢失。
至于ACID的实现原理,像 undo log 、redo log 、mvcc 、快照读和当前读,还未出现在笔者的笔记本里(小编是小白) 这里就不展开说了,后续看看要不要单独出一篇。
事务并发会带来的问题
脏读: 事务A读取到事务B未提交的数据
不可重复读:同一个事务中多次读取的数据不一致
幻读:同一个事务内多次查询返回的行数不同(一般针对插入和删除操作)
不同隔离级别的并发事务问题
隔离级别 |
脏读 |
不可重复读 |
幻读 |
读未提交(Read Uncommitted) |
是 |
是 |
是 |
读提交(Read Committed) |
否 |
是 |
是 |
可重复读(Repeatable Read) |
否 |
否 |
是 |
串行化(Serializable) |
否 |
否 |
否 |
虽然串行化可以解决事务并发带来的问题,但是效率却是最低的
一般互联网项目不会采用串行化作为隔离级别。
在mysql 中默认 可重复读
在oracle 中默认 读提交
查看与配置隔离级别
以MySQL为例(MySql存储引擎是InnoDB 才支持事务)
查看事务隔离级别:SELECT @@tx_isolation;
设置隔离级别:set session transaction isolation level ISOLATION NAME
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;
脏读、不可重复读、幻读的实践
准备工作:
Test 表数据
Id |
Age |
Name |
1 |
20 |
张三 |
2 |
25 |
李四 |
3 |
30 |
隔壁老王 |
将事务隔离级别设置为:读未提交
set session transaction isolation level read uncommitted;
脏读:
步骤 |
事务A |
事务B |
1 |
start transaction; |
|
2 |
SELECT * FROM test WHERE `name`='张三'; |
|
3 |
start transaction; |
|
4 |
UPDATE `test` SET `age`=25 WHERE `name`='张三'; |
|
5 |
SELECT * FROM `test` WHERE `name`='张三'; |
|
6 |
COMMIT; |
步骤2 结果:
Id |
Age |
Name |
1 |
20 |
张三 |
步骤5 结果:
Id |
Age |
Name |
1 |
25 |
张三 |
不可重复读:
步骤 |
事务A |
事务B |
1 |
start transaction; |
|
2 |
SELECT * FROM `test` WHERE `name`='李四'; |
|
3 |
start transaction; UPDATE `test` SET `age`=24 WHERE `name`='李四' COMMIT;
|
|
4 |
SELECT * FROM `test` WHERE `name`='李四'; |
步骤2 结果:
Id |
Age |
Name |
2 |
25 |
李四 |
步骤4 结果:
Id |
Age |
Name |
1 |
24 |
李四 |
幻读:
步骤 |
事务A |
事务B |
1 |
start transaction; |
|
2 |
SELECT * FROM `test` WHERE `age`=24; |
|
3 |
start transaction; INSERT INTO test (`age`,`name`) VALUES(24,"靠收租维持生活的小姐姐") ; COMMIT;
|
|
4 |
SELECT * FROM `test` WHERE `age`=24; |
步骤2 结果:
Id |
Age |
Name |
1 |
24 |
李四 |
步骤4 结果:
Id |
Age |
Name |
1 |
24 |
李四 |
4 |
24 |
靠收租维持生活的小姐姐 |