博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MySql中的事务
阅读量:5980 次
发布时间:2019-06-20

本文共 9511 字,大约阅读时间需要 31 分钟。

Mysql数据库是我们在开发中常用的一个开源数据库,自2001年MySql开始引入InnoDB存储引擎,并在之后一年宣布全面支持事务以来,Mysql的发展进入快车道;Mysql其优良的性能与可靠性,支持它开源数据库中独占鳌头。同时,MySql也有完善,安全的事务控制。

1.存储引擎的选择

同大多数数据库一样,Mysql中有一个存储引擎的概念,针对不同的存储需求可以选择最优的存储引擎 Mysql默认支持多种存储引擎,5.0后,包括:MyISAM、InnoDB、MEMORY、MERGE、BDB等,其中InnoDB和BDB提供事务安全表,其他存储引擎都是非事务安全表;

这里我讲的是事务的控制,所以我们在创建表的时候,都是选择的InnoDB存储引擎,它也是MySql5.5之后的默认存储引擎

2.事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。 也就是下面事务的原子性 这里我们一次银行的转账为例:A——B转帐,对应于如下两条sql语句

update from account set money=money+100 where name='B';update from account set money=money-100 where name='A';复制代码

每条sql语句可以看做一个独立的单元,这两条语句只能全部执行完成才能算这次事务执行完成

3.事务的四大特性(ACID)

3.1 原子性(Atomicity

  原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败

3.2 一致性(Consistency)

  官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。

3.3 隔离性(Isolation)

  事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

3.4 持久性(Durability)

  持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

 事务的四大特性中最麻烦的是隔离性,也是我们事务控制的主要内容  

 4.MySql数据库中操作事务的命令

默认情况下,MySql事务是自动提交(Autocommit)的,若果需要明确的Commit和Rollback来提交和回滚事务,那么就需要明确的事务控制命令来开始事务。

SET AUTOCOMMIT可以修改当前连接的提交方式,如果设置了set autocommit=0,则设置之后的所有事务都需要通过明确的命令进行提交或者回滚。如果只是对某些语句需要进行事务控制,则使用start transaction语句开始一个事务比较方便,这样事务结束之后可以自动回到自动提交的方式,如果希望所有的事务都不是自动提交的,那么通过修改AUTOCOMMIT来控制事务比较方便,这样不用在每个事务开始的时候再执行start transaction.

4.1. 创建测试sql脚本
//创建账户表CREATE TABLE `account` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(40) DEFAULT NULL,  `money` float DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;//插入测试数据INSERT INTO `account` VALUES ('1', 'A', '1000');INSERT INTO `account` VALUES ('2', 'B', '1000');INSERT INTO `account` VALUES ('3', 'C', '1000');复制代码
4.2 开启事务(start transaction)

使用"start transaction"开启MySQL数据库的事务,如下所示:

mysql> select * from account;+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1000 ||  2 | B    |  1000 ||  3 | C    |  1000 |+----+------+-------+3 rows in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)复制代码

我们首先在数据库中模拟转账失败的场景,首先执行update语句让A用户的money减少100块钱,如下所示:

mysql> update account set money=money-100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql>  select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |   900 |+----+------+-------+1 row in set (0.00 sec)复制代码

然后我们关闭当前操作的dos命令行窗口,这样就导致了刚才执行的update语句的数据库的事务没有被提交,那么我们对A用户的修改就不算是是真正的修改了,下次在查询A用户的money时,依然还是之前的1000,如下所示:

mysql> select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1000 |+----+------+-------+1 row in set (0.01 sec)复制代码
4.3 提交事务

 下面我们在数据库模拟A——B转账成功的场景:  

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)//让两个更新操作在同一事务中进行mysql> update account set money=money-100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> update account set money=money+100 where name='B';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0//提交事务mysql> commit;Query OK, 0 rows affected (0.03 sec)//转账完成mysql> select * from account;+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |   900 ||  2 | B    |  1100 ||  3 | C    |  1000 |+----+------+-------+3 rows in set (0.00 sec)复制代码

在事务提交后,A——B转账100块钱的这个业务操作算是真正成功了,A账户中少了100,B账户中多了100。

4.4 回滚事务(rollback)

通过手动回滚事务,使前面执行的操作无效

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)//对A,B进行两次update操作mysql> update account set money=money-100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> update account set money=money+100 where name='B';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> update account set money=money-100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> update account set money=money+100 where name='B';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> select * from account where name in('A','B');+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |   700 ||  2 | B    |  1300 |+----+------+-------+2 rows in set (0.00 sec)//手动回滚事务,使前面的update操作无效mysql> rollback;Query OK, 0 rows affected (0.03 sec)//A账户,B账户回到事务开始前状态mysql> select * from account where name in('A','B');+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |   900 ||  2 | B    |  1100 |+----+------+-------+2 rows in set (0.00 sec)mysql> commit;Query OK, 0 rows affected (0.00 sec)复制代码

过手动回滚事务,让所有的操作都失效,这样数据就会回到最初的初始状态。

在事务中可以通过定义SAVEPOINT,指定回滚事务的一个部分,语法:

mysql> savepoint test;Query OK, 0 rows affected (0.00 sec)复制代码

回滚时,就可以指定回滚点:

mysql> rollback to savepoint test;Query OK, 0 rows affected (0.00 sec)复制代码

5.事务的隔离级别

 多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

 5.1、事务不考虑隔离性可能会引发的问题  

  如果事务不考虑隔离性,可能会引发如下问题:

  1、脏读

脏读指一个事务读取了另外一个事务未提交的数据。

这是非常危险的,假设A向B转帐100元,对应sql语句如下所示    

1.update account set money=money+100 where name='B';    2.update account set money=money-100  where name='A复制代码

当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱。如果A等B走后再回滚,如果B再查询账户发现100元并没有到账,B就会损失100元。  

  2、不可重复读

不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。

  例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户内存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据
  很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

  3、虚读(幻读)

  虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

  如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

5.2、事务隔离性的设置语句

MySQL数据库共定义了四种隔离级别:

  • Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
  • Read committed(读已提交):可避免脏读情况发生。
  • Read uncommitted(读未提交):最低级别,以上情况均无法保证。

mysql数据库查询当前事务隔离级别:select @@tx_isolation

例如:
mysql> select @@tx_isolation    -> ;+-----------------+| @@tx_isolation  |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set, 1 warning (0.00 sec)复制代码

mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)

mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名

mysql> set transaction isolation level Read uncommitted;Query OK, 0 rows affected (0.00 sec)复制代码
5.3、使用MySQL数据库演示不同隔离级别下的并发问题

同时打开两个窗口模拟2个用户并发访问数据库

1、当把事务的隔离级别设置为read uncommitted时,会引发脏读、不可重复读和虚读

A窗口

//设置隔离级别为未提交mysql> set transaction isolation level read uncommitted;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 | money |+----+------+-------+|  1 | A    |  1000 ||  2 | B    |  1000 ||  3 | C    |  1000 |+----+------+-------+3 rows in set (0.01 sec)//这里读了B事务未提交的脏数据mysql> select * from account;+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1100 ||  2 | B    |  1000 ||  3 | C    |  1000 |+----+------+-------+3 rows in set (0.00 sec)复制代码

B窗口   

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> update account set money=money+100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0//这里不要提交,到A窗口查询A账户mysql>复制代码

2、当把事务的隔离级别设置为read committed时,会引发不可重复读和虚读,但避免了脏读

A窗口    

set transaction isolation level  read committed;start transaction;select * from account;--发现a帐户是1000元,转到b窗口select * from account; --发现a帐户多了100,这时候,a读到了别的事务提交的数据,两次读取a帐户读到的是不同的结果(不可重复读)复制代码

B窗口    

start transaction;update account set money=money+100 where name='aaa'; commit;--转到a窗口复制代码

3、当把事务的隔离级别设置为repeatable read(mysql默认级别)时,会引发虚读,但避免了脏读、不可重复读 mysql在执行select操作时,也可以避免幻读,但是在执行update、delete、insert操作时,仍然会有幻读

MySql在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)
具体可看:
具体看个例子:
A窗口:

mysql> start transaction; --使用默认隔离级别 repeatable readQuery OK, 0 rows affected (0.00 sec)//A账户初始状态mysql> select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1000 |+----+------+-------+1 row in set (0.00 sec)//B事务提交后再次查看,A账户未发生变化,避免了不可重复读mysql> select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1000 |+----+------+-------+1 row in set (0.00 sec)mysql> update account set money=money+100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0//这里只更新了一次,但是结果却不一致,引发了幻读mysql> select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1200 |+----+------+-------+1 row in set (0.00 sec)mysql> commit;Query OK, 0 rows affected (0.03 sec)mysql> select * from account where name='A';+----+------+-------+| id | name | money |+----+------+-------+|  1 | A    |  1200 |+----+------+-------+1 row in set (0.00 sec)mysql>复制代码

B窗口:

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)//修改A账户mysql> update account set money=money+100 where name='A';Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> commit;Query OK, 0 rows affected (0.13 sec)//提交后转到A窗口查看mysql>复制代码

4、当把事务的隔离级别设置为Serializable时,会避免所有问题

A窗口    

set transaction isolation level Serializable;start transaction;select * from account;--转到b窗口复制代码

B窗口    

start transaction;insert into account(name,money) values('ggg',1000);--发现不能插入,只能等待a结束事务才能插入复制代码

在选择事务隔离级别时,一般不推荐使用串行化,一是默认的repeatable read以能应付大多数并发情况,二是使用串行化会加大系统开销,执行效率大大降低

转载地址:http://mrlox.baihongyu.com/

你可能感兴趣的文章
JQuery DOM 事件冒泡
查看>>
今日开博,学习、进步
查看>>
学习笔记-小甲鱼Python3学习第十二讲:一个打了激素的数组3
查看>>
基于caffe与MATLAB接口回归分析与可视化
查看>>
Node 与 Thrift
查看>>
STL中空间配置器(allocator)的简单实现
查看>>
UIImage ImageNamed不cache
查看>>
Linux版本时间线
查看>>
手动安装AutoMySQLbackup
查看>>
CCFlow表单设计器,从内外部数据源导入生成表单
查看>>
dedecms避免专题下的内容页面调用自身
查看>>
js Promise 语法
查看>>
jstl标签库
查看>>
webmin vsftpd 模块
查看>>
python 动态变量名赋值
查看>>
nginx 利用 rewrite 屏蔽IE浏览器
查看>>
shell判断文件,目录是否存在或者具有权限
查看>>
网站以前ico图标替换新图后还显示以前的图标
查看>>
linux关于处理文本内容的命令!!
查看>>
1-1.c
查看>>