MySql中的事务

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

1.存储引擎的选择

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

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

2.事务的概念

事务指逻辑上的一组操做,组成这组操做的各个单元,要不所有成功,要不所有不成功。 也就是下面事务的原子性 这里咱们一次银行的转帐为例:A——B转账,对应于以下两条sql语句
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,这个就是事务的一致性。
bash

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数据库的事务,以下所示:ui

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.一、事务不考虑隔离性可能会引起的问题  

  若是事务不考虑隔离性,可能会引起以下问题:

  一、脏读

脏读指一个事务读取了另一个事务未提交的数据。
这是很是危险的,假设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元。  

  二、不可重复读

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

  三、虚读(幻读)

  虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,致使先后读取不一致。
  如丙存款100元未提交,这时银行作报表统计account表中全部用户的总额为500元,而后丙提交了,这时银行再统计发现账户为600元了,形成虚读一样会使银行不知所措,到底以哪一个为准。

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

5.二、事务隔离性的设置语句

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.三、使用MySQL数据库演示不一样隔离级别下的并发问题

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

一、当把事务的隔离级别设置为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>
复制代码

二、当把事务的隔离级别设置为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窗口
复制代码

三、当把事务的隔离级别设置为repeatable read(mysql默认级别)时,会引起虚读,但避免了脏读、不可重复读 mysql在执行select操做时,也能够避免幻读,可是在执行update、delete、insert操做时,仍然会有幻读
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>
复制代码

四、当把事务的隔离级别设置为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以能应付大多数并发状况,二是使用串行化会加大系统开销,执行效率大大下降

相关文章
相关标签/搜索