前言
TCL:Transaction Control Language,事务控制语言
事务:在MySQL数据库中表示一条或多条Sql语句组合在一起的一个执行单元。
这个执行单元要么全部执行,要么全部不执行,否则就会出现逻辑错误!
比如银行里的转账这个事情:
A账号余额:1000
B账号余额:1000
现在A转500元给B,那么完成这个转账的事务,数据中的SQL应该是这样的执行过程:
步骤1:A账号上要减少500元
update 储蓄表 set A.余额=A.余额-500 where 账号名='A';
步骤2:B账号上要增加500元
update 储蓄表 set B.余额=B.余额+500 where 账号名='B';
如果没有事务处理这个功能,上面的情况下,很可能会发生这样的情况:
步骤1执行成功 A的余额变为:500
刚开始执行步骤2的时候,突然出现某系统错误,导致步骤2执行失败!
步骤1成功 步骤2失败:A的钱减少了,B的钱没增加!
所以在类似的场景需求中我们需要事务处理:实现将步骤1和步骤2的SQL语句绑定在一起,要么都执行成功,要么不管是步骤1执行出错还是2出错,数据库里的数据状态会回滚到没有执行任何步骤1或2的SQL语句之前!
1.MySQL数据中的存储引擎
在具体讲事务之前,还是说说MySQL数据中的存储引擎:innoDB
1.什么是存储引擎:在mysql中的数据使用各种不同的技术来存储在磁盘文件(或内存)
当中的,这种具体的存储技术就是我们说的存储引擎。
2.我们可以通过show engines;命令来查看mysql支持的存储引擎。
3.在mysql可以选择的这些存储引擎中,innodb,myisam,memory这三个是最常用的,但其中只有innodb支持事务处理,而其他是不支持事务处理的。
2.事务的ACID特点:
- 原子性(Atomicity): 组成事务的SQL语句不可再分,要么都执行,要么都不执行。
-
一致性(Consistency): 事务必须让数据的数据状态变化到另一个一致性的状态,比如:
刚刚的例子中A和B的余额总和是2000,转账后,A和B的余额总和不能变。前后具有一致性。 - 隔离性(Isolation): 一个事务的执行,不受其他事务的干扰,相互应该是隔离的,但是实际上是很难做到的,要通过隔离级别做选择!
- 持久性(Durability): 一个事务被提交,并成功执行,那么它对数据的修改就是永久性的,接下来的其他操作或出现的故障,不能影响到它执行的结果!
3.MySQL的事务的创建:
1.隐视事务:事务没有明显的开始和结束的标记,这时候像insert语句,update语句和delete语句,每一条SQL语句就默认是一个事务。
显然,隐视事务在类似转账的逻辑业务需求的时候,就无法处理了!
2.显示事务:说白了,这个事务模式,就要我们的中程序手动的用命令来开启事务,和结束事务,并让事务里的多余SQL语句去执行。
注意:默认MySQL是开启自动提交事务的,用show variables like ‘autocommit’;命令可以查看到。所以,开启显示事务前,需要 关掉它,用set autocommit=0;只对本身回话有效。
Show ENGINES;
show VARIABLES like 'autocommit';
#@1.开始事务
SET autocommit = 0;
show VARIABLES like 'autocommit';
START TRANSACTION;#可选的,执行set autocommit=0已经默认开启了!
#@2编写事务中的SQL语句(主要是:SELECT UPDATE DELETE INSERT等语句)
语句1;语句;.......
#@3结束事务
END
commit; :提交事务去真正执行 # 或者 rollback; :回滚事务,恢复数据库执行前等状态!
示例:
DROP TABLE IF EXISTS account;
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
balance DOUBLE
);
INSERT INTO account(username,balance ) VALUES('A',1000),('B',1000);
START TRANSACTION;
#实现A账号转帐500元到B账号
UPDATE account SET balance = 500 WHERE username = 'A';
UPDATE account SET balance = 1500 WHERE username = 'B';
ROLLBACK; #事务回滚
SELECT * FROM account;
START TRANSACTION;
#实现A账号转帐500元到B账号
UPDATE account SET balance = 500 WHERE username = 'A';
UPDATE account SET balance = 1500 WHERE username = 'B';
COMMIT; #事务执行
SELECT * FROM account;
4.运行多事务导致多并发问题
同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制,就会导致各种并发问题 , 比如
4.1 脏读(没有被提交的操作)
对于两个事务 T1,T2, T1 读取了已经被 T2 更新但还没有被提交的字段之后, 若T2 回滚, T1读取的内容就是临时且无效的。T1:张飞女朋友转500元给张飞,但是没有提交事务T1T2:张飞看账户余额500元(开心坏了)然后女朋友撤销500元转账操作(T1回滚),那么张飞看到的500元是临时无效的数据,是脏读的数据。
4.2 不可重复读(在脏读基础之上,更新update操作)
对于两个事务T1, T2, T1读取了一个字段, 然后T2更新了该字段之后, T1再次读取同一个字段, 值就不同了。张飞第一次读账户余额500元张飞第二次读账户余额0元
4.3 幻读(插入insert/删除delete)
对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后T2在该表中插入了一些新的行之后, 如果T1 再次读取同一个表, 就会多出几行。张飞:请班级班上同学吃饭(班上就两位同学)然后在没有请客之前,班上有来了一位同学(由原来的请两位同学吃饭、变成请三位同学吃饭,感觉出现了幻觉)
5.隔离级别
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响, 避免各种并发问题。
当数据库系统采用read Commited隔离级别时,会导致不可重复读喝第二类丢失更新的并发问题,可以在应用程序中采用悲观锁或乐观锁来避免这类问题。从应用程序的角度,锁可以分为以下几类:
Serializable(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新。
Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
Read Commited(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新
Read Uncomitted(读未提交数据):一个事务在执行过程中可以拷打其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以有优先考虑把数据库系统的隔离级别设为Read Commited,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
当数据库系统采用read Commited隔离级别时,会导致不可重复读喝第二类丢失更新的并发问题,可以在应用程序中采用悲观锁或乐观锁来避免这类问题。从应用程序的角度,锁可以分为以下几类:
A.悲观锁:指在应用程序中显示的为数据资源加锁。尽管能防止丢失更新和不可重复读这类并发问题,但是它会影响并发性能,因此应该谨慎地使用。
B.乐观锁:乐观锁假定当前事务操作数据资源时,不回有其他事务同时访问该数据资源,因此完全依靠数据库的隔离级别来自动管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。
5.保存点(SAVEPOINT) 回滚
我们可以在MySQL处理过程中定义保存点(SAVEPOINT),然后回滚到指定的保存点前的状态。
定义保存点,以及回滚到指定保存点前状态的语法如下。
- 定义保存点---SAVEPOINT 保存点名;
- 回滚到指定保存点---ROLLBACK TO SAVEPOINT 保存点名:
下面演示将向表user中连续插入3条数据,在插入第2条数据的后面定义一个保存点,最后看看能否回滚到此保存点。
1.查看user表中的数据
SELECT * from account;
2.MySQL事务开始
BEGIN;
3.向表user中插入2条数据
INSERT INTO account(username,balance ) VALUES('C',1000),('D',1000);
SELECT * from account;
4.指定保存点,保存点名为test
SAVEPOINT test;
5.向表user中插入1条数据
INSERT INTO account(username,balance ) VALUES('E',1000);
SELECT * from account;
6.回滚到保存点test
ROLLBACK TO SAVEPOINT test;
SELECT * from account;