数据库事务浅析 + 隔离级别分析
数据库事务,简称为ACID,它是用来保证在对数据库进行并发操作时的数据安全,就像java中在并发时为保证线程安全而采用的锁一样,数据库中为保证线程安全则采用了事务,事务本身也是以锁和并发结合为基础的。
事务之所以又称为ACID,是因为这是它必须要满足的四个性质的首字母的缩写,那么是哪四个性质呢?
1、原子性(Atomicity)
所谓原子性就是指事务中的所有操作,要么全部完成,要么全部不完成,不能只完成一部分,如果有部分操作未完成而数据库中又发生了异常,那么就必须回滚到执行第一条操作之前的状态。
2、一致性(Consistency)
一致性是指数据库在执行一个事务前和执行完一个事务之后,需要从一个一致性状态转移到另一个一致性状态。
比如A转帐给B,既要在A的账户减少一定数目的钱,也要在B的账户上增加一定数目的钱,这个平衡就叫做一致性。
3、隔离性(Isolation)
隔离性是指当多个进程并发访问数据库时,如果访问的是同一张表,那么数据库为每一个进程开辟的事务之间是不能相互影响的,即并发执行的事务应该达到一种近似同步执行(而非异步)的效果,这样各个事务间的操作才能够互相隔离开来,而关于隔离性的要求不同可以将其分成不同的事务隔离级别,我们在后面将会对其进行详细介绍。
4、持久性(Durability)
持久性是指一个事务一旦提交成功,那么它对数据库的影响将是永久的,不论数据库之后进行何种操作,甚至发生异常,都不能否认之前事务对其产生的影响。
--------------------
而如果数据库是在事务的执行过程中发生了异常,那么会直接回滚到事务执行前的状态,这点在前面也已经讲过一遍了。
上面就是数据库事务的四个基本特性(ACID)了:原子性,一致性,隔离性,持久性。
不过还没有结束,大家也看得出来,上面最麻烦的就是隔离性了,其他几个性质我们甚至可以当成理所当然,但是隔离性的实际操作起来光是听起来就很复杂(尽管数据库系统已经帮我们搞定了),那么下面就介绍一下事务的隔离级别把。
事务如果不具有隔离性,那么在实际执行的时候会出现很多的问题,毕竟是多个线程同时操作,情况比较复杂,随机性大。那么如果没有隔离性,会出现哪些问题呢?
1、 脏读
脏读是指事务A读取了事务B还未提交的数据,而如果事务B回滚了,那么事务A读到的数据就是错误的数据,这里就叫做脏数据。
举个例子,B向A转帐:
update account set money=money+10 where name = 'A';
update account set money=mone-10 where name = 'B'; /*语法错误,money少写了一个字母,数据库发生异常,事务回滚*/
上面的例子中我故意将第二条sql语句写错,那么这个事务在执行到第二条操作的时候就会回滚。
可是如果事务没有隔离性,在这个事务还只执行了第一条操作的时候,A来查询他的账户,会发现他的账户多了十块钱了,可是当这个事务执行到第二条操作并回退后,A账户多出来的十块钱被回退掉了,也就是这时候A账户里实际所有的钱和A查询到的钱不一样多,这就称读到了脏数据,即脏读。
2、不可重复读
这是指事务A需要多次读取同一个属性,而事务B在A两次读取该属性的间隔中,对这个属性进行了修改并将事务提交了,那么A每次读到的该属性的值都是不同的,这就是不可重复读。
不可重复读和脏读的区别在于,脏读是在事务还没有提交的时候读取,不可重复读是一个事务执行过程中另一个事务执行了数据更新操作并进行了提交。
看起来似乎不可重复读不是个问题,以最新读取到的数据为准即可,但是实际上并不这样。举个不可重复读的例子:
假设银行工作人员需要将查询结果先显示到屏幕,再读入到文件中,那么如果显示到屏幕上的数据和写入到文件中的数据不同呢?
3、幻读(虚读)
幻读是数据插入时没有进行隔离而产生的问题。
比如事务A将表中T列全部的值都修改为了‘2’,但是如果在这个事务还未提交之前,事务B向该表中插入了一条记录并提交,而新插入的记录中第T列的值为’1’,这时当事务A再次读取T列中全部数据时,会发现还有一行的值并没有被修改,就好像产生了幻觉一样,因此叫幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(脏读是读还没有提交的),不可重复读强调的是多次读取同一个数据(所读取到的值不同),而幻读强调的是读取一批数据(比如数据的条数、总和等等)。
针对上述三种情况,数据库引入了四种不同的隔离级别,下列隔离级别从高到低排列。
隔离级别 | 效果 |
---|---|
Serializable (串行化) | 可避免脏读、不可重复读、幻读 |
Repeatable read (可重复读) | 可避免脏读、不可重复读 |
Read committed (读已提交) | 可避免脏读 |
Read uncommitted (读未提交) | 什么都不能避免 |
隔离级别越高,隔离效果越好,但是数据库的并发效率也就越低。
如果是Serializable的话,那么数据库就是真正的同步执行了,数据库会进行锁表的操作,即只一次只允许一个事务访问一张表,其他想要访问该表的事务都需要组成一个等待队列。而一般数据库都是将隔离级别设置为Repeatable read。
下面简单介绍一下这四种隔离下数据库干的事:
隔离级别 | 数据库干的事 |
---|---|
Serializable (串行化) | 锁表,因为是串行操作,因此可以避免所有的问题 |
Repeatable read (可重复读) | 对正在操作的数据加锁,在该事务执行完成前其他事务不能修改,即对需要操作的数据进行加锁,事务完成后才释放,因此避免了不可重复读,但是并不限制别的事务进行提交,因此无法避免幻读 |
Read committed (读已提交) | 还没提交的数据对别的事务不可见,因此避免了脏读,但是事务只是正在读取数据时加上读锁,一旦读取完就释放读锁,而不是等到事务结束了再释放锁,因此有可能引起不可重复读 |
Read uncommitted (读未提交) | 啥也不干,其他线程可以看到还未提交的数据,因此可以发生所有有可能产生的后果 |
最后再总结一下:
- Serializable:对整个表进行加锁,事务执行完后释放,所有事务串成一串,依次执行;
- Repeatable read:对需要操作的数据加锁,直到事务执行完后释放锁,在此期间其他事务可以向表中插入数据,也可以对其他未加锁的数据进行操作;
- Read commited:正在读取数据的事务只对该行加了读取锁,并且在读取完后就释放读取锁,而其他事务可以在释放读取锁后对数据进行修改并提交,而前一个进程再次读取时就会造成不可重复读了。注意上面两个锁是针对读和写的,而这个是针对读的,上面两个锁是事务提交后才释放锁,这个是读完数据后立即释放锁。
- Read uncommitted:啥都不干…
参考资料:
https://www.cnblogs.com/fjdingsd/p/5273008.html
https://blog.csdn.net/zzti_erlie/article/details/81094178
http://www.cnblogs.com/xdp-gacl/p/3984001.html
https://www.cnblogs.com/huanongying/p/7021555.html
https://www.cnblogs.com/xiaozhihome/p/4042698.html