MySQL的四种事务隔离级别

(一)事务的基本要素(ACID)mysql

①:原子性(Atomicity):【针对该事务的操做】事务开始后全部操做,要么所有作完,要么所有不作sql

②:一致性(Consistency):【针对数据库状态要改 一块儿改。】事务开始前和结束后--->数据库完整性约束没有被破坏。eg:就像我给你转帐,不可能我扣了钱,而你没收到钱。数据库

③:隔离性(Isolation):【针对事务与事务之间】一个事物所作的修改在最终提交以前,对其余事务不可见的。session

④:持久性(durability):【针对数据库保存】一旦事务提交后,所作的修改会永久保存数据库中。此时即便系统崩溃了。修改的数据也不会消失。并发

(二)事务并发产生的问题spa

①第一类丢失更新【更新的丢了】:在没有事务隔离的状况下,两个事物都同时更新一行数据,可是第二个事务中途失败退出,致使对数据的修改失效。rest

eg:小明的工资是1000块,事务A获取工资1000,事务B汇入100块,并提交到数据库,工资变为1100。以后呢,事务A发生异常,回滚了,回复小明工资为1000块---->这就致使事务B的更新数据丢失了。code

脏读【读的错了】:事务A读取了事务B更新的数据,而后事务B回滚了,这样A读取到的数据就是脏数据。blog

③:不可重复读【修改数据时】:在A事物内屡次取同一数据。在A事务还没有提交时,另外一个B事务修改了同一数据,A再次读到的数据就可能不同。eg:事务

小明的工资为1000,事务A中把他的工资改成2000,但事务A还没有提交。   与此同时,事务B正在读取小明的工资,读取到张三的工资为2000。   随后,事务A发生异常,而回滚了事务。张三的工资又回滚为1000。   最后,事务B读取到的张三工资为2000的数据即为脏数据,事务B作了一次脏读。

 

④:第二类丢失更新【更新覆盖了】:不可重复读的特例。有A、B两个事物同时读取一行数据,而后A对它修改该提交,而B也进行了修改提交。这回形成A事物操做失效。

⑤:幻读【新增、删除数据时】:事务不独立执行发生的一种现象。eg:系统管理员A将数据库中全部学生成绩从具体分数该为ABCD等级制,但同时,系统管理员B在这个时候插入了一条具体的分数记录。当A修改结束后发现还有一条记录没有修改过来,就好像发生了幻觉同样。

(三)MySQL事务的隔离级别

 

(1)READ_UNCOMMITTED【未提交读---不多用】   这是事务最低的隔离级别,它充许另一个事务能够看到这个事务未提交的数据。   解决第一类丢失更新的问题,可是会出现脏读不可重复读、第二类丢失更新的问题幻读 。 (2)READ_COMMITTED【提交读---大多数数据库默认的隔离级别(MySQL不是)】   保证一个事务修改的数据提交后才能被另一个事务读取,即另一个事务不能读取该事务未提交的数据。   解决第一类丢失更新和脏读【√】的问题,但会出现不可重复读第二类丢失更新的问题,幻读问题【×】 (3)REPEATABLE_READ【可重复读---MySQL默认的隔离级别(不会锁住,读取到的行)】   保证一个事务相同条件下先后两次获取数据是一致的 解决第一类丢失更新,脏读、不可重复读、第二类丢失更新的问题,但会出幻读。 (4)SERIALIZABLE【可串行化,最高隔离级别---不多用(每行加锁-->超时,锁争用)】   事务被处理为顺序执行。   解决全部问题

 

(四)具体的一些列子:

 

  一、读未提交:

 

    (1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

 

 

 

    (2)在客户端A的事务提交以前,打开另外一个客户端B,更新表account:

 

 

 

 

 

    (3)这时,虽然客户端B的事务还没提交,可是客户端A就能够查询到B已经更新的数据:

 

 

 

    (4)一旦客户端B的事务由于某种缘由回滚,全部的操做都将会被撤销,那客户端A查询到的数据其实就是脏数据:

 

 

 

     (5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,竟然是400,是否是很奇怪,数据的一致性没问啊,若是你这么想就太天真 了,在应用程序中,咱们会用400-50=350,并不知道其余会话回滚了,要想解决这个问题能够采用读已提交的隔离级别

 

 

 

 

  二、读已提交

 

    (1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的初始值:

 

 

 

    (2)在客户端A的事务提交以前,打开另外一个客户端B,更新表account:

 

 

 

    (3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

 

 

 

    (4)客户端B的事务提交

 

 

    (5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题,在应用程序中,假设咱们处于客户端A的会话,查询到lilei的balance为450,可是其余事务将lilei的balance值改成400,咱们并不知道,若是用450这个值去作其余操做,是有问题的,不过这个几率真的很小哦,要想避免这个问题,能够采用可重复读的隔离级别

 

 

 

 

   三、可重复读

 

     (1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的初始值:

 

 

    (2)在客户端A的事务提交以前,打开另外一个客户端B,更新表account并提交,客户端B的事务竟然能够修改客户端A事务查询到的行,也就是mysql的可重复读不会锁住事务查询到的行,这一点出乎个人意料,sql标准中事务隔离级别为可重复读时,读写操做要锁行的,mysql竟然没有锁,我了个去。在应用程序中要注意给行加锁,否则你会以步骤(1)中lilei的balance为400做为中间值去作其余操做

 

 

    (3)在客户端A执行步骤(1)的查询:

 

 

    (4)执行步骤(1),lilei的balance仍然是400与步骤(1)查询结果一致,没有出现不可重复读的 问题;接着执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,因此是300,数据的一致性却是没有被破坏,这个有点神奇,也许是mysql的特点吧

 

mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 400 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec) mysql> update account set balance = balance - 50 where id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 300 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec)

 

    (5) 在客户端A开启事务,查询表account的初始值

 

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 300 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec)

 

    (6)在客户端B开启事务,新增一条数据,其中balance字段值为600,并提交

 

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into account values(4,'lily',600); Query OK, 1 row affected (0.00 sec) mysql> commit; Query OK, 0 rows affected (0.01 sec)

 

    (7) 在客户端A计算balance之和,值为300+16000+2400=18700,没有把客户端B的值算进去,客户端A提交后再计算balance之和,竟然变成了19300,这是由于把客户端B的600算进去了

 

,站在客户的角度,客户是看不到客户端B的,它会以为是天下掉馅饼了,多了600块,这就是幻读,站在开发者的角度,数据的 一致性并无破坏。可是在应用程序中,咱们得代码可能会把18700提交给用户了,若是你必定要避免这状况小几率情况的发生,那么就要采起下面要介绍的事务隔离级别“串行化”

 

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)

 

  

 

  4.串行化

 

    (1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:

 

mysql> set session transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 10000 | | 2 | hanmei | 10000 | | 3 | lucy | 10000 | | 4 | lily | 10000 | +------+--------+---------+ 4 rows in set (0.00 sec)

 

    (2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,所以不会出现幻读的状况,这种隔离级别并发性极低,每每一个事务霸占了一张表,其余成千上万个事务只有干瞪眼,得等他用完提交才可使用,开发中不多会用到。

 

mysql> set session transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into account values(5,'tom',0); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
相关文章
相关标签/搜索