猿思考系列6——事务也就那么回事儿

微信扫一扫,分享到朋友圈

猿思考系列6——事务也就那么回事儿

看完上一个章节,相信你已经充分的掌握代理的套路,猿人工厂君也知道,内容对于新手而言,理解起来还是比较很吃力的,文中提到的其他方式,大家可以去尝试实现,猿人工厂君就不一一赘述了。今天我们将开启一个新的方向,让大家思考一些新的问题。不过上一章节涉及编译原理、类加载机制和一点点jvm的知识,很重要,请务必掌握其中的过程和概念。

猿思考是一个原创系列文章,帮助你从一个小白快速掌握基础知识,很多基础知识,在于思考的变通,更多精彩内容,敬请大家关注公主号 猿人工厂 ,点击 猿人养成获取

数据库事务:一般来讲是指数据库访问逻辑中一个最小的不可再分的工作单元;是一组对数据库的相对完整的逻辑单元操作。一个数据库事务,可以是一条SQL语句,也可以是一组SQL语句。

我们来看一个例子,比如,账户A向账户B转账100元,那么账户A必须减少100元,而账户B必须增加100元。这个操作是不可能分开的,是一个原子操作。账户A必须减少100元而账户B必须增加100元,不能出现A减少100元,B没有增加100元,也不能出现A没有减少100元,B增加了100元。操作必须是同时成功的或者同时失败的,必须是一致的。A和B的操作,是不能受到其他事务的干扰的从而影响结果的正确执行,并发的事务之间是需要隔离起来的,而且这个操作进行之后,A账户,和B账户的操作结果不可改变,必须把操作数据记录到磁盘,这是一个持久状态。

以上就是数据库事务必须具备的四大特性。原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability),俗称ACID四大特性或四大原则。

关于MYSQL事务的操作有几个语句你也要了解一下:

开启事务:starttransaction;(开启显示提交事务)

提交事务:commit;(提交事务,执行后,数据固化到磁盘,不可改变)

回滚事务:rollback;(回滚事务,撤销之前的DML语句操作)

在很多人的观念里,提到数据事务就是ACID四大原则,但是这四大原则是由谁来保障的呢?答案当然是数据库了。比如MYSQL数据库,你用MYISAM引擎的表搞搞事务试试?所以,要做数据库事务得有一个大前提,数据库必须是支持事务的。

如果使用的是INNODB引擎的表,默认情况下是开启了事务自动提交的,在不使用 start transation语句的情况下,任何一个insert update语句都是自动提交的,另外,insert update语句都是一个原子级操作噢,总不能写一条数据,或者修改一条数据还存在,只修改一半的情况吧?这一点先了解下,以后的学习中自由妙用。

额,这个问题看上去有点难,我们先看看原子性——要么执行,要么全部不执行。操作多条语句,思考起来太复杂了,来简单点而的。比如我们要修改用户ID为1的电话好嘛为13888888888:

update travel_user set travel_user_phone=’13888888888’where travel_user_id=1;

我们先考虑执行失败怎么办?执行失败了,自然需要保证数据恢复原样。那么要干的第一件事情,自然是记录原始数据了,有了原始记录,在之后的修改过程中,发生了任何问题,都可以恢复原样。那么第二件事情,自然是修改记录了,如果修改失败了,根据原始记录恢复数据就好了。第三件事情,是将修改的记录写到磁盘了,如果失败,有原始记录,恢复就好,如果成功,操作就完成了,事务也成功完成了。

想想看,这个原始记录需要记录在哪里?白纸黑字的东西才会让人放心,自然是文件了。Mysql提供了一个叫redo log的家伙来做这个事情。

MYSQL当然不会这么傻啦,肯定先写到内存嘛,内存操作就快了,内存写差不多了,单独起一个线程来异步把内存里的数据往磁盘上写就好了。当然,数据在内存中放时间长了,万一机房断电,数据就没有了,这个风险嘛,交给用户去配置,让他自己决定多长时间刷新到磁盘持久就好了。当然,正向操作要记录,反向操作的也要记录啦,比如记录了一条insert,必然记录一条delete用于数据恢复,就叫undo log好了。

当然考虑到其他问题,比如集群下的数据同步,MYSQL还提供了一个叫binlog的东西用于记录数据的逻辑操作——执行的SQL语句。这样你修改主节点的数据,只需要主节点把binlog的语句发到其他节点执行就好了噢。

那么新的问题又来了,redo log 的内容必须是和binlog是一致的,要不就乱套了噢。怎么来解决呢?

这个好办,先写redo log,如果写失败了,那我们就认为这次事务有问题,回滚,不再写binlog。如果redo log写成功了,写binlog写一半失败了,事务回滚嘛,然后删除无效的binlog就好了(不删除,被同步就麻烦了)。如果写redo log和binlog都成功了,事务完成,加个标记表示成功嘛。这个过程还有个学名——“二阶段提交”。

prepare :redo log刷新到磁盘,事务进入 prepare 状态。

commit::binlog刷新到磁盘,事务进入 commit 状态,并打上log_xid标记。

在讨论这个话题之前,我们来看几个问题,假如有两个事务并发执行可能发生什么情况呢?一个事务把另一个事务的数据覆盖了,这个叫丢失更新问题。一个事务A读取了一条d1,一个事务B写入一个数据,但是未提交,事务A再次查询,读到未提交的数据,这叫“脏读”。一个事务A读取了一条d1,一个事务B修改了d1,并提交事务,事务A再次读取d1,发现两条记录不一致,这就是虚读。一个事务A做了一次查询,事务B写入了数据d2,并提交事务,事务A再做了一次查询(语句可以不同),发现查询结果包含了d2,这就叫幻读。虚读和幻读都叫“不可重复读”。

为了解决这些问题数据库定义了四种隔离级别:

读未提交:read uncommitted,事务A未提交的数据事务B也能读取到,允许脏读,但是不允许发生丢失更新的问题。

读已提交:read committed,事务A已提交的数据事务B才能读取到,不允许脏读,允许发生不可重复读。

可重复读:repeatable read,事务A提交之后的数据,事务B读取不到,但是,事务B可重复读,允许幻读,但不允许虚读,即同一条记录的情况。MYSQL的默认级别就是repeatable read。

串行化:serializable,事务排队执行,一个事务执行完才能执行下一个,事务排队执行,性能可想而知,几乎无用武之地。

那MYSQL是怎么来保证事务的隔离级别的呢?

其实MYSQL在设计上比较鸡贼啦,每次开启事务都会给事务编个号,然后每一行记录都包含了三个隐藏列, DATA_TRX_ID记录最后一个事务的ID,DB_ROLL_PTR记录当前记录的redo log 信息,DB_ROW_ID用于标记新插入数据的行ID。

这下好了增删改查操作按下面的来就行了:

select:

读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本的记录。用于保证在读取之前记录都是存在的。

insert:

将当前事务的版本号保存至行的 DATA_TRX_ID

update:

新插入一行,并以当前事务版本号作为 DATA_TRX_ID ,同时将原记录行的 DATA_TRX_ID 设置为当前事务版本号

delete:

将当前事务版本号保存至行的 DATA_TRX_ID。

MYSQL会把已开始未提交的事务保存在一个叫做trx_sys的事务链表中。同时还搞了一个叫做ReadView的东西。ReadView包含了几个很重要的信息。ReadView{low_trx_id,up_trx_id, trx_ids},low_trx_id表示务链表中最大的事务id编号,up_trx_id表事务链表中最小的事务id编号,trx_ids表示所有事务链表中事务的id集合。ReadView在什么时候创建呢?这个就和隔离级别有关了。如果隔离级别是 READ COMMITTED,那么每次读取数据前都生成一个ReadView, 如果隔离级别是 REPEATABLE READ,那么在第一次读取数据时生成一个ReadView。

那么判断事务是否可见这个问题就好办了:

所有数据行上DATA_TRX_ID小于up_trx_id的记录,说明修改该行的事务在当前事务开启之前都已经提交完成,所以对当前事务来说,都是可见的。而对于DATA_TRX_ID大于low_trx_id的记录,说明修改该行记录的事务在当前事务之后,所以对于当前事务来说是不可见的。

至于位于(up_trx_id, low_trx_id)中间的事务是否可见,这个需要根据不同的事务隔离级别来确定。对于READ COMMITTED的事务隔离级别来说,对于事务执行过程中,已经提交的事务的数据,对当前事务是可见的;而对于REPEATABLE READ隔离级别来说,事务启动时,已经开始的事务链表中的事务的所有修改都是不可见的,所以在REPEATABLE READ级别下,low_trx_id基本保持与up_trx_id相同的值即可。

JS 浮点数计算BUG修复

上一篇

世界经济衰退对独角兽公司会有哪些影响?

下一篇

你也可能喜欢

猿思考系列6——事务也就那么回事儿

长按储存图像,分享给朋友