重新学习MySQL数据库开篇:数据库的前世今生

完整系列博文发布于我的专栏:重新学习MySQL数据库

数据库的前世今生

小李的数据库之旅(上)

转自:  刘欣  码农翻身  2016-09-11
1无纸化办公小李是这个大学计算机科学与技术系的知名学生,他的编程能力了得,使用Pascal 炉火纯青,这都是高中期间参加全国青少年信息学奥林匹克竞赛打下的底子,  虽然没有获过奖,但在80年代末,90年代初很多人都不知道计算机是何物的时候,人家就可以在上面写程序了, 是非常让人敬佩的事情。
所以一入学,辅导员就找到小李让他帮忙给系里开发个信息系统, 记录系里的学生信息,课程信息, 还有选课, 这样的话就可以无纸化办公了 。
小李觉得这只是一个基于命令行的程序, 无非是增删改查嘛,就满口应承下来, 然后祭出Pascal 大法,准备大干一场。 
辅导员把相关的资料也送来了, 这学生信息无非是[学号,姓名,性别,身份证号,入学日期,班级] 等信息。 课程信息也就是[课程号,课程名,授课老师] ,    选课是[学号,课程号,成绩]
有了基本的数据结构, 小李决定用三个独立的文本文件来存储这些信息, 比如说student.txt 中的内容是这样: 第一行是表头, 其他行是内容,都用逗号分开 。 剩下的两个文件的格式和这个差不多。 
编程工作进展的非常顺利, 最重要的部分无非就是用Pascal读写文件而已, 一周不到就完工了, 现在程序架构是这个样子的: 这个单机版的信息系统就这么运行了起来,效果还不错。2数据的冗余和不一致商学院的主任听说计科系有了这么一个系统, 不由的也打起来注意, 辅导员就让小李用软盘拷贝了一份过去, 商学院也顺利用来起了。
可是有些计科系的学生到商学院去选修经济学的课程时, 发现还得再输入一遍学生信息, 这实在是太烦人了。 
小李也没办法, 毕竟这是两套系统啊, 只有采用土办法, 把计科系的student.txt 复制了一份到商学院。 
这样一来数据的重复难于避免了, 更有可能出现数据不一致的地方, 比如地址信息在计科系改了, 但是商学院没改。 
后来辅导员说数学系自己也搞了一个类似的系统, 不是用Pascal而是用C写的, 数据格式和小李定义的还不一样, 小李想把Student.txt复制过去也不可能了。 
小李想要是学校所有的院系都用这么一套系统就好了。 其实学校领导也看到了这个问题, 只是现在的校内局域网还没有建立起来, 大家用同一套系统并不现实。3李氏查询到了期末, 计科系和商学院的老师纷纷给小李打电话:“小李,我想统计一下这个学期操作系统课有哪些人没及格, 多少人在80分以上, 你能帮忙弄弄吗?”
“小李,我想算一下经济学的平均分, 能不能程序实现一下? 学生太多,手工算太麻烦了 ”......
为了应付这些“变态”的需求, 小李假期几乎没怎么休息, 不停的用PASCAL写各种各样的功能。 
可是这种需求似乎无穷无尽, 总结一下,无非就是对这些文件的各种各样的查询而已。 
难道让老师们直接去文件中查找和计算吗? 显然不行。  
小李想起了一句话: “ 所有计算机的问题都可以通过增加一个中间层来解决” 
那提供一个中间层吧, 把文件层屏蔽掉, 让老师们在这个中间层用自己熟悉的术语进行查询。 
中间层上要有逻辑的数据结构,其实就是这些东西:学生信息:[学号,姓名,性别,入学日期,班级,地址] 
课程信息:[课程号,课程名,授课老师] 
选课 :[学号,课程号,成绩]

小李决定把这些东西称为“ ” ,其中的每一项称为 “列”/“字段”/“属性”, 每一列都有类型,例如字符型,日期型,数字型等等
查询的话是用类似这样进行的:  SELECT  学号,姓名  FROM 学生信息  WHERE  入学日期='1991-9-1'
想把几个表连接起来查询也可以:
SELECT 学号,姓名, 课程名,成绩 FROM 学生信息 s , 课程信息 c, 选课 scON s.学号=sc.学号 AND c.课程号=sc.课程号  WHERE   课程名='操作系统'  AND 成绩<60 
很明显小李需要写一个解析器, 把这样的语句变成内部对文件的操作, 还好小李已经有一点编译原理的基础了, 努力一下还是能写出来的。
小李把查询规则给各个老师做了个简单的培训, 从此以后, 只要不是超级复杂的查询, 老师们自己就搞定了,再也不用骚扰小李了。 
无心插柳柳成荫,小李忽然发现,自己的程序也可以调用这样的抽象层来编程啊, 也不用直接操作文件了, 简化了好多。  小李得意的把这套查询称为“李氏查询” ,  李氏查询用起来简便快捷, 最大的好处是用户完全不用考虑物理层的那些文件的结构,只需要关注逻辑层的“表”就可以了。
(码农翻身注:其实就是SQL了)
可是小李一直是隐隐觉得不安, 不知道这种查询方式有没有漏洞, 后来看到埃德加·弗兰克·科德 的论文 “A Relational Model of Data for Large Shared Data banks(大型共享数据库的关系模型)”,这才明白,其实这就是所谓的关系模型啊, 其背后的有着坚实的数学基础, 肯定是没有问题的。
有了一个中间的逻辑层, 还带来了一个额外的好处,现在小李可以对物理层的文件存储做一些优化了, 为了加快访问速度, 小李不再采用简单的逗号分隔的文件, 还增加了索引、B+树,缓存等手段。由于有中间层的存在,这些变化对应用层没有什么影响。
(未完待续)

小李的数据库之旅(下)

转自:  刘欣  码农翻身  2016-09-13

接上篇《小李的数据库之旅(上)》, 上回说到小李用一个中间逻辑层解决了普通人也能查询数据的问题, 很快新的挑战就来了。

4并发访问校园的局域网很快就建立起来, 原来单机的软件纷纷转为支持网络访问的系统, 学校为了统一各系的信息系统管理, 要从现有的系统中择优选择一个,升级成局域网可访问的, 然后全校扩展。 小李的软件和数学系的,电子系的一起竞争, 相比而言,数学系的系统采用了网状的结构, 电子系的采用了层次结构, 无论是哪种结构, 使用者都需要知道精确的内部结构以后才有可能进行查询, 相比“李氏查询” 实在是太过繁琐。  小李的系统以很大的优势胜出了。 
小李刚学会了C语言, 觉得这种语言更加贴近硬件,效率更高,更适合写这些“系统级”的软件, 于是决定保留之前的设计, 然后用C重写。  
当然不仅仅是重构, 还包含了重要的功能增强:网络访问, 从单机软件变成了客户端-服务器结构(C/S)的软件。 学校购买了一个性能强劲的IBM服务器作为服务全校的中心数据节点, 小李的软件部署在了上面, 想着自己的软件被这么多教职工使用, 小李觉得很有成就感。 
好景不长, 小李很快就发现网络版软件的复杂度要远远超过单机版, 这不马上就有老师爆出了一个超级大问题。
王老师对一个学生的地址进行了更新, 张老师对另外一个学生的地址也做了更改, 后来发现王老师的修改不见了, 这是怎么回事? 
小李看了代码,很快就发现在单机版的时候, 原来的操作都是 基于整个文件的: 读入文件内容, 做修改, 然后写入文件, 很明显, 王老师的修改在前,张老师的修改在后, 王老师的被覆盖了。 
真是个严重的问题, 恰逢周末, 小李赶紧通宵达旦的修改, 升级系统,把基于文件的操作改变成 基于行的操作: 每个人的修改只影响这一行。 
小李觉得这样应该没问题了, 可是很快就发生了两个人对同一行的修改: 
电子系的账户有1000元, 刘老师支取了300, 金老师支取了200 , 最后账户的余额竟然是800元 ! 实际应该是500元啊。 
这是个极为严重的错误, 系统被迫停止了几天专门来修复这个问题。 
一个解决的办法就是给这一行加锁, 在刘老师读取了1000元, 扣除300元,并且把700 写回到数据库之前, 不允许金老师操作,这样就不会乱掉了。5原子性问题小李找了几个同学,仔细的审查了程序,确保一些重要的更新操作都有行锁, 这次稍微松了一口气。 
可是一次非常偶然的系统故障有暴露了一个从没有想过的大问题:
当时电子系的账户有1000元, 数学系有2000元,  电子系要给数学系转账200元, 系统先扣除了电子系的账户钱的钱,变成了800 , 正要往数学系上面增加余额的时候, 系统出了故障,崩溃了。 
重启以后,就发现电子系的余额是对的, 可是数学系还是2000元, 那200元丢了 !
很明显, 转账这个操作,必须得是原子的: 要么全部发生, 要么根本不发生。 
小李决定把类似这样的操作叫做“事务”, 但是怎么实现呢?
小李苦思冥想, 终于放了一个大招: 记录日志 ! 
在做真正的操作之前,先把要做的事记录下来形成日志(Log),这个日志中包括修改的数据项标识, 数据项的旧值(修改前的值)和新值(修改后的值), 然后再进行真正的数据库修改。   
刚开始的时候事务处于活动状态, 只有所有的操作都正确无误的写入了磁盘,才会进入提交状态, 否则就要回滚修改。 
(码农翻身注: 除了原子性之外,事务还有持久性,隔离性,一致性,这里就不展开了)6安全 有一天系主任找到小李,提了一个全新的问题:“小李啊,能不能添加一点权限控制? 比方说系里的财务状况只能我和财务人员知道, 现在每个人都可以查询,这成什么样子?”
小李心想确实是这样, 一个没有权限控制的系统是非常危险的, 尤其是随意删除, 那还了得?!
赶紧加上一个权限系统, 小李想了想,  先定义三大类权限:1. 对数据操作的, 例如SELECT, UPDATE, INSERT等2. 对结构操作的, 例如创建表,修改表,等3. 做管理的, 例如备份数据, 创建用户等
然后就可以把这些权限授予某个用户了, 很多时候,还需要把表附加上, 像这样:GRANT  SELECT on 财务表 to  系主任GRANT  CREATE_TABLE to 张老师
(码农翻身注: 这里模仿了mysql)
解决了如此多棘手的问题以后, 小李的信息系统已经非常复杂了,实际上,这个系统的中间层完全可以剥离出来,形成一个完整的软件了, 小李把它称为: 数据库

你看到的只是冰山一角, 更多精彩文章,尽在“ 码农翻身” 微信公众号, 回复消息" m"或" 目录" 查看更多文章


数据库范式介绍


从这学期开始,张大胖开始学习数据库, 听说这门课很重要, 很基础, 但是大胖学的很烦。


其实刚开始的时候还行, 课程先讲了讲数据库的作用, 他听的津津有味, 但讲到后边, 当那些文绉绉的术语像关系演算、函数依赖、规范化......  出现的时候,  大胖彻底的懵了。  


他实在是不明白, 这个看起来像一个表格的东西为什么搞这么多数学的公式, 最烦数学了。


周末大胖跑去向大神好基友Bill诉苦 :“这关系数据库不就是一个二维的表格吗, 就像Excel那样, 一行一列的, 为什么搞的这么复杂, 还有数学的东西?”


Bill 笑了笑说: “看起来确实像个表格, 但是和表格很不一样, 你看看这个例子, 然后想想能直接用关系数据库来保存吗?”

(表格1 订单表 )


大胖说: “似乎不行,你看一个订单号X2001对应一个用户,U001(白展堂),  还对应两行产品, P101(路由器), P102(充电宝) , 不过, 我能不能改成这样: ”

(表格2 点击看大图)


Bill一看, 这张大胖竟然把多行合并成了一行, 中间用逗号分开, 不由的又气又笑:

“你要是参加工作了, 设计出这样的数据库表, 老板非骂死你不可。  你要记住呀, 我们关系数据库最忌讳的就是在一个单元格里存储多个值。 这是典型的‘非规范化’的设计”


“非规范化?  那怎么办? ”


“拆分, 把它拆成规范化的 ” 说着,Bill 搞了两个表格出来:


(表3: 订单表)

(表4: 订单细节表)


“明白了, 这样的拆分就可以保证一个单元格只有个值了” 张大胖说。


“这种形式的表, 我们就叫做第一范式,  不止如此, 你看看表格3 订单表, 是不是一个订单号就能唯一的确定一行? ”


“对, 订单表确实是这样的, 但是表格4 单单用订单号就不行了, 还得加上产品编码, 才能确定同一行的其他值。”


Bill 说: ”所以我们说表3的主键是 (订单号), 表4的主键是(订单号,产品编码), 这是一个复合主键“


大胖高兴的说: “啊, 这第一范式看来很简单嘛”


“别急, 你再看看表格4:订单细节表, 虽然说 (订单号,产品编码) 是主键, 能确定其他属性的值, 但是 产品名称和单价  实际上并不依赖于 订单号。  如果我们想添加一个新的产品比如ipad, 你会发现没法放入这张表, 因为没有订单号!


“奥, 必须先有订单才能有产品, 这确实是太扯了, 难道再拆分?” 大胖问?


“必须拆, 要不然就没法工作”

(表4.1  订单细节表)

(表4.2 产品表)


“我明白了, 现在表4.1 中主键还是(订单号,产品编码),  剩下的属性(数量)肯定依赖于这个主键了, 表4.2也类似。”


Bill总结说:“这种所有属性仅仅依赖于主键的情况就是 第二范式 。 ”


“这些范式术语听起来一本正经的, 很学究, 背后还是挺有用的嘛。 那表格3中主键是(订单号), 其他所有属性都依赖于主键, 已经是第二范式了吧”


Bill 说 :“可以这么认为, 但是这个表有个好玩的情况,  就是订单号能决定用户ID, 而用户ID 能决定用户名称, 这就出现了传递依赖:  订单号->用户ID->用户名称。  你看看用户信息其实也无法单独管理了, 也得拆分, 这个很简单, 你来试试? ”


张大胖迅速的鼓捣出两张表来:

(表3.1  订单表)

(表3.2  用户表)


Bill说:“不错, 现在就没有传递依赖了,  我们可以称之为 第三范式 了”


“我有个疑问啊” 大胖问道, ”为了满足所谓的范式要求, 我们把最初的大表拆的如此‘分散’, 到时候查询的时候岂不非常麻烦??“


“可不是, 把这些‘分散表’连接(Join)起来才能形成最初的那张表,  如果在数据量特别巨大的时候, 这种连接挺耗时的。 在实践中我们有时候不得不违反范式,做点数据的冗余。 比如说我们虽然把表3.2 用户表单独拆分了出来,  但是有时候为了性能, 还会在表3.1 中把用户名也加上, 为一个冗余。 ”


数据库事务隔离级别介绍

丢失的数据

旺财是数据库村的一个程序, 小强也是。


数据库村有个特点, 很多数据支持共享操作,多个程序可以同时读写,他们俩经常会为了读写同一个数据, 争夺的不可开交。


这一天,当旺财和小强对同一个银行账户A进行写操作时候, 出现了这么一个错误:

看看, 本来旺财要加上的20元就丢掉了。  


同样的事情发生的多了, 他俩给这种情况起了一个名字,叫“丢失修改”, 其实说白了就是俩人都去写一个数据, 一个人的数据把另外一个给覆盖了。


村里的Mysql说: “你们两个小家伙,写数据的时候连加锁都不做,肯定会出大乱子!"


旺财说:“加什么锁?”


“来来来, 我教你们一个排他锁(Exclusive Lock) ,   简称X锁, 旺财你要写数据了, 就把它用X锁锁住, 锁住后,除非你释放, 否则小强无法获得X锁。 这不就解决你们的问题了?  ”


小强想了想, 就把上面的操作过程用X锁改了一下:

旺财说:“果然不错, 确实可以解决两个人同时修改导致的问题。”


脏数据

小强说:“旺财, 我们约定,写数据的时候都用X锁吧?”


旺财说: “这没问题, 可是X锁只在写数据的时候用, 我们读数据是不用加锁的, 我想起了一种情况, 你看看怎么办?”

小强在旺财执行的途中读了A的值, 但是旺财把对A的修改给回滚(Rollback)了, 这下小强尴尬了, 他读到了脏数据


“要不我们在读取数据的时候也加个X锁 ? ” 小强说。


“那样太严格了, 就是读一个数据啊, 值得吗?”


“这样吧, 我们再搞一个新的锁出来, 专门用于共享数据的读取, 就叫共享锁(Share lock) ,简称S锁, 这个锁和之前的排他锁X锁有区别, 主要用于读取数据,  如果一个数据加了X锁, 就没法加S锁, 同样加了S锁, 就没法加X锁”   小强想出了一个点子。


“那如果我加了S锁, 你还能加S锁吗? ”  旺财问。


“应该可以吧,  咱们俩都是读数据, 互不影响啊。 还有为了防止长时间的锁住, 我们可以约定一下,不管我们要做的事情有多少, 读一个数据之前加S锁, 读完之后立刻释放该S锁 ! ”

果然,这样一来“脏数据”的问题就解决了 !

没法重复读?

旺财和小强两个程序相安无事了很久, 但是S锁在读完数据后立刻释放的约定, 导致出了一个新问题。


旺财在一次数据处理中, 先读取了A和B的值, 相加得到了150 ,  然后小强把B改成了30

旺财再次读取A和B, 发现求和以后是130 , 刚才的不一样了!

(码农翻身注: 假定旺财的处理是在一个事务当中)

旺财说: “小强,  我在读取数据的时候你不能改啊 , 要不然我这里会出现不一致, 你看刚开始是A+B是 150, 现在变成130了”


小强说: “我们之前的约定是读数据时加S锁, 读完立马释放,  问题就出现在这里了。”


“看来在读数据的时候, 也需要一直锁定了, 直到事务提交。”



幻觉出现

旺财和小强现在已经能灵活的使用X锁和S锁了。

他们俩总结了一下, 分为了这么几种情况:


1.  写数据时加上X锁,直到事务结束, 读的时候不加锁。

虽然能够避免丢失数据,  但是可以读到没有提交或者回滚的内容 (脏数据), 这其实就是数据库最低的事务隔离级别 --- Read uncommitted


2. 写数据的时候加上X锁, 直到事务结束,  读的时候加上S锁, 读完数据立刻释放。

这能避免“丢失数据”和“脏数据”,  但是会出现“不可重复读”的问题  ,  这是第二级的事务隔离级别 -- Read committed


3.  写数据的时候加上X锁,  直到事务结束, 读数据的时候加S锁, 也是直到事务结束。

这能避免“丢失数据”和“脏数据”, “不可重复读”三个问题 , 这是数据库常用的隔离级别 --

Repeatable read


整个世界似乎清净了。


有一次旺财对一个“学生表”进行操作,选取了年龄是18岁的所有行, 用X锁锁住, 并且做了修改。


改完以后旺财再次选择所有年龄是18岁的行, 想做一个确认, 没想到有一行竟然没有修改!

这是怎么回事?  出了幻觉吗?


原来就在旺财查询并修改的的时候,  小强也对学生表进行操作, 他插入了一个新的行,其中的年龄也是18岁!  虽然两个人的修改都没有问题, 互不影响, 但从最终效果看, 还是出了事。


(码农翻身注: 正是小强的操作, 让旺财出现了“幻读”)


旺财说: “没辙了, 我们俩非得串行执行不可, 你必须得等我执行完。 ”


这就是数据库事务隔离级别的终极大招:Serializable


最后, 为了方便记忆, 他们俩倒腾了半天, 整出了一张表, 用于记录各种情况:

(点击看大图)


两个人看着这张表, 感慨的说:“唉, 这数据库村的事务隔离级别可真是不容易啊!”


Mysql 不屑一顾的说: “这都嫌麻烦了, 你们还没遇到死锁呢....”



Mysql数据库的undo日志保证事务的正确执行

1数据库老头儿

我们这个世界很大, 生活着很多人,形形色色,各怀绝技。但是被公认为最拽的一个却是数据库老头儿,年龄挺大,每天都要炫耀几遍他那关系型数据库, 那理论有着多么坚实的数学基础,那关系运算是多么地优雅,事务管理是多么强大,还有他是多么地稳定, 要不怎么他怎么能活这么久等等。 


老头儿有他拽的资本,因为我们这个系统的核心数据都是在老头儿那里存放着, 例如用户了,订单了,交易了......    我向别人打听过, 这些数据已经在老头儿那里积累了20多年了, 最早的时候是Dephi, PowerBuider 这些上古的软件写的系统访问, 后来慢慢转到互联网,先用PHP访问, 再后来变为Java。 


看来最宝贵的东西是数据, 外界系统可以变, 但是数据不能丢, 老头儿守着这份财产,生生地熬死了一代人,估计还会再熬死一代人。


2 事务

这一天老头儿又在那里一边喝酒,一边“吹嘘” 他的事务管理,说些ACID之类我们都听不懂的外国话, 和数据库经常打交道的是年轻的小伙子Tomcat,   对于老头儿,他比我们多了解那么一点点。


Tomcat端了一杯啤酒来到老头儿桌前:“嗨,老头儿,我知道你说的事务有个重要的特性:原子性,就是说在一个事务中不管有多少操作,都是要么全做,要么全不做,是这样吧!”  


“那是自然!”


“我很好奇,在执行的操作过程中,如果还没做完系统就崩溃了,或者断电了,你怎么办啊? 你怎么保证原子性?”


听到Tomcat问了一个关键的问题,酒馆里的CPU阿甘, Ngnix, Spring, MyBatis 都围了过来,都想听听老头儿的高见 。


“如果我还没做完,系统就崩溃了,那系统重启以后我就得做恢复操作啊。”


“怎么恢复啊? ”  Tomcat 穷追不舍。


“你举个例子,我给你分析分析 ” 老头环顾四周,看到人们都围了上来,也来了一点兴致 , “光讲理论,这些小朋友们怎么听得懂?”


“好啊, 比如旺财有200块钱, 小强有50 块钱,现在旺财要给小强转账,假设转100块吧, 你说说,你是怎么实现要么不做,要么全做的。 ”  


(1)  开始事务 T1 (假设T1是个事务的内部编号)

(2)  旺财余额 = 旺财余额 -100

(3)  小强余额 = 小强余额 + 100

(4)  提交事务 T1


CPU阿甘插了一嘴说: “虽然这些都是我在做计算,但是这些计算结果都是先存在内存中的, 内存那家伙一断电啥都忘了, 如果第三步还没执行完就断电了, 那不就完蛋了。”


数据库老头儿说: “虽然数据先在内存中被计算出来, 但是我是要写入硬盘的数据库文件的,知道不? ”



“你的数据缓冲区是啥意思?”    人群里有人问道。


“老土! 连数据缓冲区都不知道! 我告诉你吧, 那个硬盘啊速度太慢了,比内存慢个几万倍,你说我能每次操作都去写硬盘吗? 绝对不可能,所以CPU阿甘计算出的数据我会放到数据缓冲区里, 我会在合适的时候把数据缓冲区的内容写入硬盘的数据文件。 ”


“什么合适的时候?”


“那是我的缓冲管理器要做的事情了,想听吗? 我再花两天给你讲讲!”


3 Undo 日志

“算了算了, 我们还是先假定数据缓冲区能和硬盘的数据文件同步吧, 回到刚才的问题,旺财在给小强转账, 第二步执行完了,旺财的余额变成了100块 (200-100), 假设已经写入了硬盘文件, 现在断电了, 小强的余额有没有加上,系统的钱白白消失了100块, 数据已经不一致了,  你怎么办?”    Tomcat把话题转移回来。


“放心吧, 我会记录日志的,我有个叫做Undo的日志文件,就是为了解决这个问题的”   老头儿喝了一口酒 ,准备开始放大招  “比如你说的情况, 我会在我的日志文件中记录下事务开始之前的他俩账号余额:


[事务T1,  旺财原有余额 , 200]

[事务T1, 小强原有余额, 50 ] 


如果事务执行到一半,就断电了,那数据库重启以后我就根据undo的日志文件来恢复。” 


“嗯,那要是系统恢复的过程中又断电了,还得再次恢复,那数据岂不变得一团糟? ”  CPU阿甘对断电心有余悸。


“对啊对啊”, 周边的人附和到。


“你们这些年轻人啊, 还是too simple,  你们仔细想想,即使我把旺财的余额和小强的余额恢复了100次,会有什么结果?”


“如果每次都试图把旺财的余额设为200, 小强余额设为50, 做多少次都没问题, 因为他俩原来的余额就是那么多 !”  Tomcat恍然大悟。


“这就叫做操作的幂等性,知道不? 我可以一直做恢复,恢复过程中断电也不怕,只要把恢复做完就行。” 老头儿看到时机一到,立刻上升为理论。 


“恢复数据的时候, 那你怎么才能知道一个事务没有完成呢?” Tomcat接着问道。


数据库老头儿对这个问题似乎很满意,  专门花点时间写了几行字:


[开始事务 T1]

[事务T1, 旺财原有余额,200]

[事务T1, 小强原有余额,50]

[提交事务 T1]


“Undo日志文件中不仅仅只有余额, 事务的开始和结束也会记录,如果我在日志文件中看到了[提交事务 T1], 或者 [回滚事务 T1], 我就知道这个事务已经结束,不用再去理会它了, 更不用去恢复。 如果我只看到 [开始事务 T1], 而找不到提交或回滚,那我就得恢复。比如下面这个:”


[开始事务 T1]

[事务T1, 旺财原有余额,200]

[事务T1, 小强原有余额,50]


“特别是,” 老头补充道, “ 我恢复以后, 需要在日志文件中加上一行 [回滚事务 T1] , 这样下一次恢复我就不用再考虑T1这个事务了。”


4 独门绝技

“不对吧, 你这个Undo日志文件会面临和数据文件一样的问题, 都是需要加载到内存才能读写, 要不然会太慢。  那要是连日志文件还没写好就断电了,那不还是玩完?” Ngnix 目光如炬,向深层次挖掘。 


这是个绝佳的问题,大家纷纷把目光杀向数据库老头儿,希望这一次能把他打翻在地。


老头儿镇定自若,没有回答,反而给大家抛出一个问题:“你们想想,我什么时候应该记录Undo日志,什么时候把Undo日志写入文件呢?”


“事务一开始的时候就得把Undo日志写入文件, 这样最安全!”  


老头儿笑了笑说: “蠢材! 一开始的时候我都不知道程序到底要操作那个字段,怎么记录Undo日志,怎么写入硬盘文件? ”


“那怎么办啊? ”


“这就是我的独门秘籍了, 我给大家举个例子, 你们可要看仔细了, 我把日志记录也放到了内存的Undo日志缓冲区,伺机写入硬盘。”


(点击看大图)

“不知道你们这些小朋友看出一点门道儿没有?” 老头儿问道。


“让我想想” Ngnix说, “如果系统在第4步和第5步之间崩溃,旺财的余额写入了硬盘,但是小强的还没写入, 那Undo日志看起来是这样的:


[开始事务 T1]

[事务T1, 旺财原有余额,200]


由于找不到事务结束的日志, 你会进行恢复操作, 把旺财的原有余额给恢复了。 ”


Tomcat 接过来说:“如果是在第7步和第8步之间系统崩溃,旺财和小强的最新余额都写入了硬盘,但是没有提交事务, 那Undo日志看起来是这样的:

[开始事务 T1]

[事务T1, 旺财原有余额,200]

[事务T1, 小强原有余额,50]


由于没有事务结束的日志,你也需要进行恢复,把旺财和小强的原有余额恢复成200和50 ”


CPU阿甘也不甘示弱说: “如果是在第8步和第9步之间系统崩溃, 旺财和小强的最新余额都写入了硬盘, 也提交了事务, 但是提交事务的操作没有写入Undo 日志, 所以Undo日志还是这样:”


[开始事务 T1]

[事务T1, 旺财原有余额,200]

[事务T1, 小强原有余额,50]


由于没有事务结束的日志,你还得需要进行恢复,把旺财和小强的原有余额恢复成200和50”


数据库老头笑眯眯的听着大家的分析,似乎非常的享受: “是不是可以应付各种情况啊? 啊?”


大家纷纷点头,佩服无比。 


Tomcat都不叫老头儿,改称为老先生了: “ 老先生,  好像是有什么内在的规律,您给说说!”


数据库老头儿无比得意, 兴致勃勃地说, “这就是我的独门秘籍了, 其实很简单了, 就两条: 


1.  在你把最新余额写入硬盘之前, 一定要先把相关的Undo日志记录写入硬盘。 例如[事务T1, 旺财原有余额,200] 一定要在旺财的新余额=100写入硬盘之前写入。


2.  [提交事务 T1] 这样的Undo日志记录一定要在所有的新余额写入硬盘之后再写入。  有了这两条的保证,我就可以高枕无忧了!, 比如说,换个操作次序也没有问题:”


(点击看大图)

大家仔细一想,果真如此啊,看来数据库老头儿, 不,数据库老先生经常自夸还真有道理,人家真的有干货啊。 

猜你喜欢

转载自blog.csdn.net/a724888/article/details/78362684