内容简介:Mysql数据库是我们在开发中常用的一个开源数据库,自2001年MySql开始引入InnoDB存储引擎,并在之后一年宣布全面支持事务以来,Mysql的发展进入快车道;Mysql其优良的性能与可靠性,支持它开源数据库中独占鳌头。同时,MySql也有完善,安全的事务控制。同大多数数据库一样,Mysql中有一个存储引擎的概念,针对不同的存储需求可以选择最优的存储引擎 Mysql默认支持多种存储引擎,5.0后,包括:MyISAM、InnoDB、MEMORY、MERGE、BDB等,其中这里我讲的是事务的控制,所以我们
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: 0 mysql> 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: 0 mysql> 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: 0 mysql> 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> 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> 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> 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窗口 复制代码
MySql在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)
具体可看: www.cnblogs.com/chinesern/p…
具体看个例子:
A窗口:
mysql> start transaction; --使用默认隔离级别 repeatable read Query 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: 0 mysql> 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以能应付大多数并发情况,二是使用串行化会加大系统开销,执行效率大大降低
以上所述就是小编给大家介绍的《MySql中的事务》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java事务解析(事务的基本操作+隔离的等级+事务的四大特性+事务的概念)
- 在云中管理事务,第 1 部分: 事务基础和分布式事务
- Spring事务专题(三)事务的基本概念,Mysql事务处理原理
- 嵌套事务、挂起事务,Spring 是怎样给事务又实现传播特性的?
- 分布式事务中使用RocketMQ的事务消息机制优化事务的处理逻辑
- 数据库事务与事务的隔离级别
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Java(第二版·中文版)
Kathy Sierra,Bert Bates 著、杨尊一 编译 张然等 改编 / 杨尊一 / 中国电力出版社 / 2007-2 / 79.00元
《Head First Java》是本完整的面向对象(object-oriented,OO)程序设计和Java的学习指导。此书是根据学习理论所设计的,让你可以从学习程序语言的基础开始一直到包括线程、网络与分布式程序等项目。最重要的,你会学会如何像个面向对象开发者一样去思考。 而且不只是读死书,你还会玩游戏、拼图、解谜题以及以意想不到的方式与Java交互。在这些活动中,你会写出一堆真正的Jav......一起来看看 《Head First Java(第二版·中文版)》 这本书的介绍吧!