事务详解(基于MySQL)

在理解事务的概念之前,接触数据库系统的其他高级特性还言之过早,这里就着重于事务进行分析,当然本节内容也并非只属于MySQL方面,对事物的学习有一定的帮助,特别是对于想从MySQL开始入手的同学这是思路清晰,解析详细的一篇学习文献。

一、事务的概念

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transactionend transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
  在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。事务内的语句,要么全部执行成功,要么全部执行失败。
1.1 为什么要用事务?
  事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
  
1.2 事务分类
1.2.1 数据库事务:
  数据库事务通常指对数据库进行读或写的一个操作序列。
设计目的:
(1)为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
(2)当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
1.2.2 系统中的事务:
  处理一系列业务处理的执行逻辑单元,该单元里的一系列类操作要不全部成功要不全部失败。

注意:
  在事务进行过程中,未结束之前,底层数据是不会被更改的,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据。

二、事务的操作术语

开启事务:begin(start transaction);
提交事务:commit(commit work);
回滚事务:rollback(rollback work);
事务结束:end transaction;
保存回滚点:savepoint xxx;
回滚到指定的回滚点:rollback to xxx;
查看数据库自动提交状态:show variables like ‘%autocommit%’;
修改自动提交状态(0—关闭,1—开启):set autocommit=0/1;

三、事务的自动提交(autocommit)

mysql的autocommit代表事务是否自动提交(0=OFF 为关闭,1=ON为开启)。mysql默认为自动提交(autocommit=1/ON),即当begin开始一个事务后,关闭mysql会话窗口,没有执行rollback或者commit操作,sql语句会自动提交。当autocommit关闭后(autocommit=0),需要执行commit才会将事务提交,关闭mysql会话后,则不会提交事务。这里的autocommit的值可以通过show variables like ‘autocommit’;查询,并可以通过set autocommit=xxx;来进行设置。

四、MySQL中事务的开启与提交模式

对于一个MYSQL数据库(InnoDB),事务的开启与提交模式无非下面这两种情况:
4.1 若参数autocommit=0
  事务则在用户本次对数据进行操作时自动开启,在用户执行commit命令时提交,用户本次对数据库开始进行操作到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。总而言之,当前情况下事务的状态是自动开启手动提交
  
4.2 若参数autocommit=1(系统默认值),事务的开启与提交又分为两种状态:
4.2.1 手动开启手动提交:
  当用户执行start transaction命令时(事务初始化),一个事务开启,当用户执行commit命令时当前事务提交。从用户执行start transaction命令到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。
4.2.2 自动开启自动提交:
  如果用户在当前情况下(参数autocommit=1)未执行start transaction命令而对数据库进行了操作,系统则默认用户对数据库的每一个操作为一个孤立的事务,也就是说用户每进行一次操作系都会即时提交或者即时回滚。这种情况下用户的每一个操作都是一个完整的事务周期

五、MySQL中事务的周期

事务的周期由用户在命令提示符中输入START TRANSACTION指令开始,直至用户输入COMMIT结束。
创建事务的一般过程是:
  初始化事务、创建事务、应用SELECT语句查询数据是否被录入和提交事务。如果用户不在操作数据库完成后执行事务提交,则系统会默认执行回滚操作。如果用户在提交事务前选择撤销事务,则用户在撤销前的所有事务将被取消,数据库系统会回到初始状态,也就是执行回滚,不保存之前的操作。
  
具体如下:
(1)初始化事务
  初始化MySQL事务,首先声明初始化MySQL事务后所有的SQL语句为一个单元。在MySQL中,应用START TRANSACTION命令来标记一个事务的开始。
  初始化事务的结构:START TRANSACTION;
  另外,用户也可以使用BEGIN或者BEGIN WORK命令初始化事务,通常START TRANSACTION命令后面跟随的是组成事务的SQL语句
  在命令提示符中输入如下命令:start transaction;
  如果在用户输入以上代码后,MySQL数据库没有给出警告提示或返回错误信息,则说明事务初始化成功,用户可以继续执行下一步操作。
  
(2)创建事务
这里就是具体的sql执行语句。如:
insert into connection(email, cellphone, QQ, sid)
values(‘barrystephen@126.com’,13456000000,187034000,3);

(3)应用SELECT语句查询数据是否被正确输入
SELECT * FROM connection WHERE sid=3;
  在用户插入新表为“InnoDB”类型或更改原来表类型为“InnoDB”时,如果在输入命令提示后,MySQL提示“The ‘InnoDB’ feature is disabled;you needInnoDB’ to have it working”警告,则说明InnoDB表类型并没有被开启,用户需要找到MySQL文件目录下的“my.ini”文件,定位“skip_innodb”选项位置,将原来的“skip_innodb”改为“#skip_innodb”后保存该文件,重新启动MySQL服务器,即可令数据库支持“InnoDB”类型表。
  
(4)提交事务
  在用户没有提交事务之前,当其他用户连接MySQL服务器时,应用SELECT语句查询结果,则不会显示没有提交的事务。当且仅当用户成功提交事务后,其他用户才可能通过SELECT语句查询事务结果,由事务的特性可知,事务具有孤立性,当事务处在处理过程中,其实MySQL并未将结果写入磁盘中,这样一来,这些正在处理的事务相对其他用户是不可见的。一旦数据被正确插入,用户可以使用COMMIT命令提交事务。
  提交事务的命令结构:COMMIT;
  一旦当前执行事务的用户提交当前事务,则其他用户就可以通过会话查询结果。
  
(5)撤销事务(事务回滚)
  撤销事务,又被称作事务回滚。即事务被用户开启、用户输入的SQL语句被执行后,如果用户想要撤销刚才的数据库操作,可使用ROLLBACK命令撤销数据库中的所有变化。
  事务回滚命令结构:ROLLBACK;
  输入回滚操作后,如何判断是否执行回滚操作了呢?可以通过SELECT语句查看插入的数据是否存在.
  如果执行一个回滚操作,则在输入START TRANSACTIONA命令后的所有SQL语句都将执行回滚操作。故在执行事务回滚前,用户需要慎重选择执行回滚操作。如果用户开启事务后,没有提交事务,则事务默认为自动回滚状态,即不保存用户之前的任何操作。
注意:
  事务不支持嵌套功能,当用户在未结束第一个事务又重新打开一个事务,则前一个事务会自动提交,同样MySQL命令中很多命令都会隐藏执行COMMIT命令。

六、事务的特性(ACID)

3.1原子性(atomicity)
  一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

3.2一致性(consistency)
  数据库总是从一个一致性的状态转换到另外一个一致性的状态。

3.3隔离性 (isolation)
  通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。

3.4持久性(durability)
  一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。持久性是个有点模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有能做到100%的持久性保证的策略(如果数据库本身就能做到真正的持久性,那么备份又怎么能增加持久性呢?)。
  
  就像锁粒度的升级会增加系统开销一样,这种事务处理过程中额外的安全性,也会需要数据库系统做更多的额外工作。一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。这里也体现了存储引擎的选择是见仁见智的。

七、事务的隔离级别

隔离性其实比想象得要复杂。在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
7.1 四种隔离级别
7.1.1 read uncommitted(未提交读)
  在read uncommitted级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,read uncommittied不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。
  未提交读什么问题都不能解决。
  
7.1.2 read committed(提交读)
  大多数数据库系统的默认隔离级别都是read committed(但MySQL不是)。read committed满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。
  提交读可以解决脏读,是Oracle的默认事务隔离级别。
  
7.1.3 repeatable read(可重复读)
  repeatable read解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题
  可重复读可以解决脏读和不可重复读,是MySQL的默认事务隔离级别。

7.1.4 serializable(可串行化)
  serializable是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
  可串行化可以解决脏读、不可重复读和幻读,该隔离级别在读写数据时会锁住整张表。
 
7.2 事务的并发问题
7.2.1 脏读
针对未提交数据)如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。

7.2.2 不可重复读
  一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
  (针对其他提交前后,读取数据本身的对比)不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的,所以,Read Uncommitted也无法避免不可重复读取的问题。
  
7.2.3 幻读
  当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。
  (针对其他提交前后,读取数据条数的对比) 幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下, 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的,所以,Read Uncommitted同样无法避免幻读的问题。

7.3 ANSI SQL隔离级别

隔离级别脏读可能性不可重复读可能性幻读可能性加锁读
read uncommittedyesyesyesno
read committednoyesyesno
repeatable readnonoyesno
serializablenononoyes

7.4 MySQL查看及设置事物隔离级别
7.4.1 查看隔离级别
select @@tx_isolation;

7.4.2 设置隔离级别
(1)全局修改,修改mysql.ini配置文件,在最后加上
#可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.
[mysqld]
transaction-isolation = REPEATABLE-READ

这里全局默认是REPEATABLE-READ,其实MySQL本来默认也是这个级别
(2)对当前session修改,在登录mysql客户端后,执行命令:
set [global|session] transaction isolation level+isolation-level
其中isolation-level可以:
– READ UNCOMMITTED
– READ COMMITTED
– REPEATABLE READ
– SERIALIZABLE

7.5 注意点:
(1)事务隔离级别为读提交时,写数据只会锁住相应的行。
(2)事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
(3)事务隔离级别为串行化时,读写数据都会锁住整张表。
(4)隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

八、事务ACID的实现原理

8.1 Mysql怎么保证一致性的?
这个问题分为两个层面来说。
  (1)从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。
  但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。
  (2)从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!
  
8.2 Mysql怎么保证原子性的?
利用Innodb的undo log
  undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。
例如:
(1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据
(2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作
(3)当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作
  undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
ps:undo log日志见其他博客。

8.3 Mysql怎么保证持久性的?
Innodb的redo log
  正如之前说的,Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。
怎么解决这个问题?
  事务提交前直接把数据写入磁盘就行啊。
这么做有什么问题?
  只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。
  毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。
  于是,决定采用redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。
采用redo log的好处?
  其实好处就是将redo log进行刷盘比对数据页刷盘效率高,具体表现如下:
redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
ps:redo log日志见其他博客。

8.4 Mysql怎么保证隔离性的?
利用的是锁和MVCC机制
  至于MVCC,即多版本并发控制(Multi Version Concurrency Control),一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。
  如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本。
  由于MVCC机制在可重复读(Repeateable Read)和读已提交(Read Commited)的MVCC表现形式不同,就不赘述了。
  但是有一点说明一下,在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值