事务普遍的运用于订单系统、银行系统等多种场景。若是有如下一个场景:A用户和B用户是银行的储户。如今A要给B转帐500元。那么须要作如下几件事:mysql
1. 检查A的帐户余额>500元;sql
2. A帐户扣除500元;数据库
3. B帐户增长500元;安全
正常的流程走下来,A帐户扣了500,B帐户加了500,皆大欢喜。那若是A帐户扣了钱以后,系统出故障了呢?A白白损失了500,而B也没有收到本该属于他的500。以上的案例中,隐藏着一个前提条件:A扣钱和B加钱,要么同时成功,要么同时失败。事务的需求就在于此。服务器
解决方案:当 A 帐户的余额减小以后,不要当即修改数据表,而是在确认 B 帐户的余额增长以后,同时修改数据表。并发
事务安全,是一种保护连续操做同时实现(完成)的机制。事务安全的意义就是,保证数据操做的完整性。性能
一、事务的定义atom
事务(Transaction):一个最小的不可再分的工做单元;spa
一般一个事务对应一个完整的业务(例如银行帐户转帐业务,该业务就是一个最小的工做单元)。.net
一个完整的业务须要批量的DML(insert、update、delete)语句共同联合完成。
事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不一样,DML语句的个数不一样。
二、存储引擎(为何要提MySQL的存储引擎,由于不是全部的数据库存储引擎都支持事务)。
在实际工做中,选择一个合适的存储引擎是一个比较复杂的问题。每种存储引擎都有本身的优缺点,不能笼统地说谁比谁好。
InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制。若是须要对事务的完整性要求比较高(好比银行),要求实现并发控制(好比售票),那选择InnoDB有很大的优点。若是须要频繁的更新、删除操做的数据库,也能够选择InnoDB,由于支持事务的提交(commit)和回滚(rollback)。
MyISAM:插入数据快,空间和内存使用比较低。若是表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。若是应用的完整性、并发性要求比 较低,也可使用。
MEMORY:全部的数据都在内存中,数据的处理速度快,可是安全性不高。若是须要很快的读写速度,对数据的安全性要求较低,能够选择MEMOEY。它对表的大小有要求,不能创建太大的表。因此,这类数据库只使用在相对较小的数据库表。
注意:同一个数据库也可使用多种存储引擎的表。若是一个表要求比较高的事务处理,能够选择InnoDB。这个数据库中能够将查询要求比较高的表选择MyISAM存储。若是该数据库须要一个用于查询的临时表,能够选择MEMORY存储引擎。
三、事务的四大特性
四、事务的并发访问问题
1)脏读:脏读就是指当一个事务正在访问数据,而且对数据进行了修改,而这种修改尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。
例如:
张三的工资为5000,事务A中把他的工资改成8000,但事务A还没有提交。
与此同时,
事务B正在读取张三的工资,读取到张三的工资为8000。
随后,
事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,
事务B读取到的张三工资为8000的数据即为脏数据,事务B作了一次脏读。
2)不可重复读:是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的的数据多是不同的。这样就发生了在一个事务内两次读到的数据是不同的,所以称为是不可重复读。
例如:
在事务A中,读取到张三的工资为5000,操做没有完成,事务还没提交。
与此同时,
事务B把张三的工资改成8000,并提交了事务。
随后,
在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中先后两次读取的结果并不致,致使了不可重复读。
3)幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的所有数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,之后就会发生操做第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉同样。
例如:
目前工资为5000的员工有10人,事务A读取全部工资为5000的人数为10人。
此时,
事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
五、事务的隔离级别
事务的隔离性受到隔离级别的影响。那么事务的隔离级别是什么呢?事务的隔离级别能够认为是事务的"自私"程度,它定义了事务之间的可见性。隔离级别分为如下几种:
1).READ UNCOMMITTED(未提交读)。在RU的隔离级别下,事务A对数据作的修改,即便没有提交,对于事务B来讲也是可见的,这种问题叫脏读。这是隔离程度较低的一种隔离级别,在实际运用中会引发不少问题,所以通常不经常使用。
2).READ COMMITTED(提交读)。在RC的隔离级别下,不会出现脏读的问题。事务A对数据作的修改,提交以后会对事务B可见,举例,事务B开启时读到数据1,接下来事务A开启,把这个数据改为2,提交,B再次读取这个数据,会读到最新的数据2。在RC的隔离级别下,会出现不可重复读的问题。这个隔离级别是许多数据库的默认隔离级别。
3).REPEATABLE READ(可重复读)。在RR的隔离级别下,不会出现不可重复读的问题。事务A对数据作的修改,提交以后,对于先于事务A开启的事务是不可见的。举例,事务B开启时读到数据1,接下来事务A开启,把这个数据改为2,提交,B再次读取这个数据,仍然只能读到1。在RR的隔离级别下,会出现幻读的问题。幻读的意思是,当某个事务在读取某个范围内的值的时候,另一个事务在这个范围内插入了新记录,那么以前的事务再次读取这个范围的值,会读取到新插入的数据。Mysql默认的隔离级别是RR,然而mysql的innoDB引擎间隙锁成功解决了幻读的问题。
4).SERIALIZABLE(可串行化)。可串行化是最高的隔离级别。这种隔离级别强制要求全部事物串行执行,在这种隔离级别下,读取的每行数据都加锁,会致使大量的锁征用问题,性能最差。
设置事务的隔离级别:
方式一:能够在my.ini文件中使用transaction-isolation选项来设置服务器的缺省事务隔离级别
该选项值能够是:
– READ-UNCOMMITTED
– READ-COMMITTED
– REPEATABLE-READ
– SERIALIZABLE
• 例如: [mysqld] transaction-isolation = READ-COMMITTED
方式二:经过命令动态设置隔离级别
隔离级别也能够在运行的服务器中动态设置,应使用SET TRANSACTION ISOLATION LEVEL语句。
其语法模式为:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
其中的<isolation-level>能够是:
– READ UNCOMMITTED
– READ COMMITTED
– REPEATABLE READ
– SERIALIZABLE
• 例如: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
六、事务的一些术语及两条重要的SQL语句
在MySQL中,默认状况下,事务是自动提交的,也就是说,只要执行一条DML语句就开启了事物,而且提交了事务(固然,自动提交机制也是能够关闭的)。
事务操做,分为两种:自动事务(默认的),手动事务。
以银行帐户的余额增减为例,咱们来了解手动事务的操做流程。
执行以下 SQL 语句,建立银行帐户表并插入数据:
-- 建立银行帐户表
create table bank_account(
id int primary key auto_increment,
cardno varchar(16) not null unique comment 'bank card number',
name varchar(20) not null,
money decimal(10,2) default 0.0 comment 'account balance'
)charset utf8;
-- 插入数据
insert into bank_account values
(null, '0000000000000001', 'Charies', 8000),
(null, '0000000000000002', 'Gavin', 6000);
操做事务:
第 1 步:开启事务,告诉系统如下全部操做,不要直接写入数据库,先存到事务日志。
start transaction;
执行如上 SQL 语句,开启事务:
第 2 步:减小 Charies 帐户的余额
-- 更新 Charies 帐户余额
update bank_account set money = money - 1000 where id = 1;
-- 查询 bank_account 表数据
select * from bank_account;
如图所示:
Charies 帐户的余额显示减小1000
,但实际上,因为咱们开启了事务,数据表真实的数据,并无同步更新。为了验证这个论断,咱们从新打开一个数据库客户端,查询bank_account
表的数据:
如上图所示:
显然数据库的事务安全机制起了做用,当咱们开启(手动)事务以后,其后一系列操做并无直接写入数据库,而是存入了事务日志。在这里,咱们并无打开数据库事务的日志进行验证,由于事务日志存储的是通过编译以后的字节码文件。
第 3 步:增长 Gavin 帐户的余额
-- 更新 Gavin 帐户余额
update bank_account set money = money + 1000 where id = 2;
-- 查询 bank_account 表数据
select * from bank_account;
如上图所示:
Gavin 帐户的余额显示增长1000
,可是,因为咱们开启了事务,数据表真实的数据,仍然没有同步更新。
第 4 步:提交事务或回滚事务
若是咱们选择提交事务,则将事务日志存储的记录直接更新到数据库,并清除事务日志;若是咱们选择回滚事务,则直接将事务日志清除,全部在开启事务至回滚事务之间的操做失效,保持原有的数据库记录不变。在这里,咱们以提交事务为例:
-- 提交事务
commit;
-- 查询 bank_account 表数据
select * from bank_account;
如上图所示:
当咱们提交事务以后,数据库的真实记录更新,两个客户端的数据一致。
值得咱们注意的是:
当咱们提交事务以后,在进行回滚事务是不起做用的,由于事务日志在提交事务的同时已经被清除了!
参考博客:https://blog.csdn.net/qq_35246620/article/details/78305872