略微加速

略速 - 互联网笔记

数据库四大隔离级别

2020-09-15 leiting (1102阅读)

标签 数据库

隔离性:

将数据库设计成单线程的数据库,可以防止所有的线程安全问题,自然就保证了隔离性.但是如果数据库设计成这样,那么效率就会极其低下.

数据库在想不保证隔离性,可能会发生的情况:如果是两个线程并发修改,一定会互相捣乱,这时必须利用锁机制防止多个线程的并发修改。如果两个线程并发查询,没有线程安全问题。如果两个线程一个修改,一个查询,就会导致脏读,不可重复读,虚读(幻读)。具体想做什么级别的控制,数据库无法帮我们做主,让我们自己决定。

脏读:一个事务读取到另一个事务未提交的数据(比如A和B买鞋,A汇钱给B,汇钱这个操作还没有提交,A告诉B我打钱了,B查了一下,发现了A的汇的钱。给了A鞋子,A立刻回滚,B发现自己账户上面没有钱)

不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同--- 行级别的问题(A在银行里面活期有1000元,定期有1000,固定资产有1000。B是银行职员,上司B查一下A在银行行里面总共有多少钱。B查到A活期,定期,固定资各1000.因为并发执行,这时A刚好过来取走了活期1000元,B此时在并发执行,统计A的总钱数,发现是2000元。B提交给上司,上司发现钱数目不对)

虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致 --- 表级别的问题(A银行账户1000元,B有1000元。D是银行业务人员,上司让D统计银行到底总共有多少钱,每个人有多少钱。D查询,发现总共2000元,银行用户总共2人,D刚要算平均值时。C来了,向银行里面存了4000元,D此时再算平均钱,结果是每人2000元。结果上次发现前后数据又不一致)。

对于这些情况,并发修改,数据库肯定要用锁。并发查询,没问题。一个修改,一个查询,数据库提供了四大隔离级别。

四大隔离级别:

Read uncommitted -- 不防止任何隔离性问题,具有脏读/不可重复度/虚读(幻读)问题

Read committed -- 可以防止脏读问题,但是不能防止不可重复度/虚读(幻读)问题

Repeatable read -- 可以防止脏读/不可重复读问题,但是不能防止虚读(幻读)问题

Serializable -- 数据库被设计为单线程数据库,可以防止上述所有问题

从安全性上考虑: Serializable>Repeatable read>read committed>readuncommitted

从效率上考虑: read uncommitted>read committed>Repeatableread>Serializable

真正使用数据的时候,根据自己使用数据库的需求,综合分析对安全性和对效率的要求,选择一个隔离级别使数据库运行在这个隔离级别上.mysql 默认下就是Repeatable read隔离级别,oracle 默认下就是read committed个隔离级别。

查询当前数据库的隔离级别:select@@tx_isolation;设置隔离级别:set [global/session] transactionisolation level xxxx;其中如果不写默认是session指的是修改当前客户端和数据库交互时的隔离级别,而如果使用golbal,则修改的是数据库的默认隔离级别。

数据库中的锁机制:

共享锁(读锁):在非Serializable隔离级别做查询不加任何锁,而在Serializable隔离级别下做的查询加共享锁。

共享锁的特点:共享锁和共享锁可以共存,但是共享锁和排他锁不能共存

排他锁(互斥锁、写锁):在所有隔离级别下进行增删改的操作都会加排他锁,

排他锁的特点:和任意其他锁都不能共存

Serialize级别下,你要查询,加共享锁。现在另一个人想要修改数据,需要排他锁。共享锁和排他锁不能共存,所以只能先查数据,另一个人等你查完在修改。自然不会有脏读、虚读、幻读等问题。任意级别下,两个线程要并发修改数据,需要排他锁,只要有一个线程修改,其他线程不可修改,需要等待。在非serialize级别下,查询不需要锁,修改要排他锁,可能会有脏读、虚读、幻读等问题。A和B在serialize级别下,都在查询数据,各有一个共享锁。现在A想要修改数据,共享锁升级为排他锁,需要等待B释放共享锁。B也要修改数据。共享锁升级为排他锁,需要等待A释放共享锁。造成死锁,mysql会在死锁发生时,结束造成死锁的一段,让另一端运行。

============================================

最近在看高性能MYSQL一书,所以对其进行例子分析已巩固自己的印象

  数据库的事务操作其实就是一组原子性的操作,要么全部操作成功,要么全部操作失败。

  

  比如说我需要对外销售1张电影票,且登记一下销售信息到另一个表,至少需要以下3个步骤

  1.查询电影票数量是否满足销售1张电影票 SELECT remain_count FROM cinema WHERE film_id = 123456789;

  2.更新电影票数量 UPDATE remain_count = remain_count -1 FROM cinema WHERE film_id=123456789;

  3.插入销售信息   INSERT  INTO sell_mes(id ,mes) values(id,mes);

  试想一下如果我们其中的一步被出错了或者被其他操作打乱就很容易出现问题。比如说有两个销售系统A,B在销售同样的票,此时票只剩下1张,A接到订单要售出一张票,他查看电影票的数量大于1,于是要售出的时候,也就是在第一步执行完毕执行第二步的时候,B也接到订单,也看到余票大于1,B也要售出1张票。此时就出现了余票只有1张却售出两张的荒唐的情况出现。

  所以一个良好的系统是需要通过ACID测试,至于ACID是什么,就自行百度吧,这篇博文讲的是隔离性的隔离级别以及例子

  而事务的隔离级别有四种,隔离级别高的数据库的可靠性高,但并发量低,而隔离级别低的数据库可靠性低,但并发量高,系统开销小

  

  1.READ UNCIMMITTED(未提交读)

  事务中的修改,即使没有提交,其他事务也可以看得到,比如说上面的两步这种现象就叫做脏读,这种隔离级别会引起很多问题,如无必要,不要随便使用

  例子:还是售票系统,小明和小花是售票员,他们分别是两个不同窗口的员工,现在售票系统只剩下3张票,此时A来小华这里买3张票,B来小明买票,小华查到余票还有就给接了订单,就要执行第三步的时候,小明接到B的请求查询有没有余票。看到小华卖出了3张票,于是拒绝卖票。但是小华系统出了问题,第三步执行失败,数据库为保证原子性,数据进行了回滚,也就是说一张票都没卖出去。

  总结:这就是事务还没提交,而别的事务可以看到他其中修改的数据的后果,也就是脏读。

  

  2.READ COMMITTED(提交读)

  大多数数据库系统的默认隔离级别是READ COMMITTED,这种隔离级别就是一个事务的开始,只能看到已经完成的事务的结果,正在执行的,是无法被其他事务看到的。这种级别会出现读取旧数据的现象

  例子:还是小明小华销售员,余票3张,A来小华那里请求3张订票单,小华接受订单,要卖出3张票,上面的销售步骤执行中的时候,B也来小明那里买票,由于小华的销售事务执行到一半,小明事务没有看到小华的事务执行,读到的票数是3,准备接受订单的时候,小华的销售事务完成了,此时小明的系统变成显示0张票,小明刚想按下鼠标点击接受订单的手又连忙缩了回去。

  总结:这就是小华的事务执行到一半,而小明看不到他执行的操作,所以看到的是旧数据,也就是无效的数据

 

  3.REPEATABLE READ(可重复读)

   REPEATABLE READ解决了脏读的问题,该级别保证了每行的记录的结果是一致的,也就是上面说的读了旧数据的问题,但是却无法解决另一个问题,幻行,顾名思义就是突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,导致多次读取的时候,数据的行数不一致。

  例子:销售部门有规定,如果销售记录低于规定的值,要扣工资,此时经理在后端控制台查看了一下小明的销售记录,发现销售记录达不到规定的次数,心里暗喜,准备打印好销售清单,理直气壮和小明提出,没想到打印出来的时候发现销售清单里面销售数量增多了几条,刚刚好达到要求,气的经理撕了清单纸。原来是小明在就要打印的瞬间卖出了几张票,因此避过了减工资的血光之灾。

  总结:虽然读取同一条数据可以保证一致性,但是却不能保证没有插入新的数据

 

  4.SERIALIZABLE(可串行化)

  SERIALIZABLE是最高的隔离级别,它通过强制事务串行执行(注意是串行),避免了前面的幻读情况,由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别


北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3