数据库的范式和ACID原理

来源:互联网 发布:热电偶数据采集器 编辑:程序博客网 时间:2024/04/29 18:19

数据库三大范式详解

作者:佚名    文章来源:本站原创    点击数:33234    更新时间:2009-8-7

数据库范式1NF 2NF 3NF BCNF(实例)

    设计范式(范式,数据库设计范式,数据库的设计范式)是符合某一种级别的关系模式的集合。构造数据库必须遵循一定的规则。在关系数据库中,这种规则就是范式。关系数据库中的关系必须满足一定的要求,即满足不同的范式。目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、第四范式(4NF)、第五范式(5NF)和第六范式(6NF)。满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。下面我们举例介绍第一范式(1NF)、第二范式(2NF)和第三范式(3NF)。 
    在创建一个数据库的过程中,范化是将其转化为一些表的过程,这种方法可以使从数据库得到的结果更加明确。这样可能使数据库产生重复数据,从而导致创建多余的表。范化是在识别数据库中的数据元素、关系,以及定义所需的表和各表中的项目这些初始工作之后的一个细化的过程。 
    下面是范化的一个例子 Customer Item purchased Purchase price Thomas Shirt $40 Maria Tennis shoes $35 Evelyn Shirt $40 Pajaro Trousers $25 
如果上面这个表用于保存物品的价格,而你想要删除其中的一个顾客,这时你就必须同时删除一个价格。范化就是要解决这个问题,你可以将这个表化为两个表,一个用于存储每个顾客和他所买物品的信息,另一个用于存储每件产品和其价格的信息,这样对其中一个表做添加或删除操作就不会影响另一个表。

关系数据库的几种设计范式介绍

1 第一范式(1NF)

    在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 
    所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。例如,对于图3-2 中的员工信息表,不能将员工信息都放在一列中显示,也不能将其中的两列或多列在一列中显示;员工信息表的每一行只表示一个员工的信息,一个员工的信息在表中只出现一次。简而言之,第一范式就是无重复的列。

2 第二范式(2NF)

    第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。如图3-2 员工信息表中加上了员工编号(emp_id)列,因为每个员工的员工编号是惟一的,因此每个员工可以被惟一区分。这个惟一属性列被称为主关键字或主键、主码。 
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。

3 第三范式(3NF) 

    满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在图3-2的员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。

数据库设计三大范式应用实例剖析 

     数据库的设计范式是数据库设计所需要满足的规范,满足这些规范的数据库是简洁的、结构明晰的,同时,不会发生插入(insert)、删除(delete)和更新(update)操作异常。反之则是乱七八糟,不仅给数据库的编程人员制造麻烦,而且面目可憎,可能存储了大量不需要的冗余信息。 
    设计范式是不是很难懂呢?非也,大学教材上给我们一堆数学公式我们当然看不懂,也记不住。所以我们很多人就根本不按照范式来设计数据库。 
实质上,设计范式用很形象、很简洁的话语就能说清楚,道明白。本文将对范式进行通俗地说明,并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。

范式说明 

    第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

    例如,如下的数据库表是符合第一范式的:

    字段1 字段2 字段3 字段4

    而这样的数据库表是不符合第一范式的:

    字段1 字段2 字段3 字段4 
    字段3.1 字段3.2 

    很显然,在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。 

    第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

    假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分),关键字为组合关键字(学号, 课程名称),因为存在如下决定关系: 
    (学号, 课程名称) → (姓名, 年龄, 成绩, 学分) 

    这个数据库表不满足第二范式,因为存在如下决定关系: 
    (课程名称) → (学分) 
    (学号) → (姓名, 年龄) 
    即存在组合关键字中的字段决定非关键字的情况。 

    由于不符合2NF,这个选课关系表会存在如下问题: 
    (1) 数据冗余: 
    同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。 
    (2) 更新异常: 
    若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。 
    (3) 插入异常: 
    假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。 
    (4) 删除异常: 
    假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。 

    把选课关系表SelectCourse改为如下三个表: 
    学生:Student(学号, 姓名, 年龄); 
    课程:Course(课程名称, 学分); 
    选课关系:SelectCourse(学号, 课程名称, 成绩)。 

    这样的数据库表是符合第二范式的, 消除了数据冗余、更新异常、插入异常和删除异常。 
    另外,所有单关键字的数据库表都符合第二范式,因为不可能存在组合关键字。

    第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系: 
    关键字段 → 非关键字段x → 非关键字段y 

    假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话),关键字为单一关键字"学号",因为存在如下决定关系: 
    (学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话) 

    这个数据库是符合2NF的,但是不符合3NF,因为存在如下决定关系: 
    (学号) → (所在学院) → (学院地点, 学院电话) 
    即存在非关键字段"学院地点"、"学院电话"对关键字段"学号"的传递函数依赖。 
    
    它也会存在数据冗余、更新异常、插入异常和删除异常的情况,读者可自行分析得知。 
    把学生关系表分为如下两个表: 
    学生:(学号, 姓名, 年龄, 所在学院); 
    学院:(学院, 地点, 电话)。 

    这样的数据库表是符合第三范式的,消除了数据冗余、更新异常、插入异常和删除异常。 
鲍依斯-科得范式(BCNF):在第三范式的基础上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。 

    假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系: 
    (仓库ID, 存储物品ID) →(管理员ID, 数量) 
    (管理员ID, 存储物品ID) → (仓库ID, 数量) 
    所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系: 
    (仓库ID) → (管理员ID) 
    (管理员ID) → (仓库ID) 
    即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况: 
    (1) 删除异常: 
    当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。 
    (2) 插入异常: 
    当仓库没有存储任何物品时,无法给仓库分配管理员。 
    (3) 更新异常: 
    如果仓库换了管理员,则表中所有行的管理员ID都要修改。 

    把仓库管理关系表分解为二个关系表: 
    仓库管理:StorehouseManage(仓库ID, 管理员ID); 
    仓库:Storehouse(仓库ID, 存储物品ID, 数量)。 
    这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。 

范式应用 

    我们来逐步搞定一个论坛的数据库,有如下信息: 
    (1) 用户:用户名,email,主页,电话,联系地址 
    (2) 帖子:发帖标题,发帖内容,回复标题,回复内容 

    第一次我们将数据库设计为仅仅存在表: 
    用户名 email 主页 电话 联系地址 发帖标题 发帖内容 回复标题 回复内容 
    这个数据库表符合第一范式,但是没有任何一组候选关键字能决定数据库表的整行,唯一的关键字段用户名也不能完全决定整个元组。我们需要增加"发帖ID"、"回复ID"字段,即将表修改为: 
    用户名 email 主页 电话 联系地址 发帖ID 发帖标题 发帖内容 回复ID 回复标题 回复内容 
    这样数据表中的关键字(用户名,发帖ID,回复ID)能决定整行: 
    (用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容) 
    但是,这样的设计不符合第二范式,因为存在如下决定关系: 
    (用户名) → (email,主页,电话,联系地址) 
    (发帖ID) → (发帖标题,发帖内容) 
    (回复ID) → (回复标题,回复内容) 
    即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。 

我们将数据库表分解为(带下划线的为关键字): 
(1) 用户信息:用户名,email,主页,电话,联系地址 
(2) 帖子信息:发帖ID,标题,内容 
(3) 回复信息:回复ID,标题,内容 
(4) 发贴:用户名,发帖ID 
(5) 回复:发帖ID,回复ID 

    这样的设计是满足第1、2、3范式和BCNF范式要求的,但是这样的设计是不是最好的呢? 
不一定。 

    观察可知,第4项"发帖"中的"用户名"和"发帖ID"之间是1:N的关系,因此我们可以把"发帖"合并到第2项的"帖子信息"中;第5项"回复"中的"发帖ID"和"回复ID"之间也是1:N的关系,因此我们可以把"回复"合并到第3项的"回复信息"中。这样可以一定量地减少数据冗余,新的设计为: 
(1) 用户信息:用户名,email,主页,电话,联系地址 
(2) 帖子信息:用户名,发帖ID,标题,内容 
(3) 回复信息:发帖ID,回复ID,标题,内容 

    数据库表1显然满足所有范式的要求; 

    数据库表2中存在非关键字“标题”、“内容”对关键字段“发帖ID”的部分函数依赖,即不满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常; 

    数据库表3中也存在非关键字段"标题"、"内容"对关键字段"回复ID"的部分函数依赖,也不满足第二范式的要求,但是与数据库表2相似,这一设计也不会导致数据冗余和操作异常。 

    由此可以看出,并不一定要强行满足范式的要求,对于1:N关系,当1的一边合并到N的那边后,N的那边就不再满足第二范式了,但是这种设计反而比较好! 

    对于M:N的关系,不能将M一边或N一边合并到另一边去,这样会导致不符合范式要求,同时导致操作异常和数据冗余。 

    对于1:1的关系,我们可以将左边的1或者右边的1合并到另一边去,设计导致不符合范式要求,但是并不会导致操作异常和数据冗余。

结论 

    满足范式要求的数据库设计是结构清晰的,同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的,在数据库表中存在1:1或1:N关系这种较特殊的情况下,合并导致的不符合范式要求反而是合理的。 

    在我们设计数据库的时候,一定要时刻考虑范式的要求。

文章录入:詹璐    责任编辑:詹璐 


数据库一二三BC范式详解

分类: 数据库 2060人阅读 评论(0) 收藏 举报
数据库存储c

目录(?)[+]

1.范式说明

1.1 第一范式(1NF)无重复的列

  所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能同时有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。

  在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。在当前的任何关系数据库管理系统(DBMS)中,不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。

举例1:

一张学生表Student(stuNo,stuName,age,age,sex)是不符合第一范式的,因为有重复列age属性。去除重复列age以后的Student(stuNo,stuName,age,sex)是符合第一范式的。

1.2 第二范式(2NF)属性完全依赖于主键 [ 消除部分子函数依赖 ]

  第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被唯一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。例如员工信息表中加上了员工编号(emp_id)列,因为每个员工的员工编号是唯一的,因此每个员工可以被唯一区分。这个唯一属性列被称为主关键字或主键、主码。

  第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是属性完全依赖于主键。

  这里说的主关键字可能不只有一个,有些情况下是存在联合主键的,就是主键有多个属性。

举例2:

以学生选课为例,每个学生都可以选课,并且有这一门课程的成绩,那么如果将这些信息都放在一张表StuGrade(stuNo,stuName,age,sex,courseNo,courseName,credit,score)。如果不仔细看,我们会以为这张表的主键是stuNo,但是当我们看到最后一个score属性以后,在想想如果没有课程信息,那么哪里有学生成绩信息呢。所以这张表的主键是一个联合主键(stuNo,corseNo),这个联合属性能够唯一确定score属性。那么再看其他信息,比如stuName只需要stuNo就能够唯一确定,courseName只需要courseNo就能够唯一确定,因此这样就存在了部分依赖,不符合第二范式。如果要让学生课程成绩信息满足第二范式,那么久需要将这张表拆分成多张表,一张学生表Studnet(stuNo,stuName,age,sex),一张课程表Course(courseNo,courseName,credit),还有最后一张学生课程成绩表StuGrade(stuNo,courseNo,score)。这样就符合第二范式了。

1.3 第三范式(3NF)属性不依赖于其它非主属性 [ 消除传递依赖 ]

  满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息

举例3:

  每一个员工都有一个所属部门,假如有一个员工信息表Employee(emp_id,emp_name,emp_age,dept_id,dept_name,dept_info)。这张员工信息表的属性是emp_id,因为这个属性能够唯一确定其他所有属性,比如知道员工编号emp_id以后,肯定能够知道员工姓名,所属部门编号,部门名称和部门介绍。所以这里dept_id不是主属性,而是非主属性。但是,我们又可以发现dept_name,dept_info这两个属性也可以由dept_id这个非主属性决定,即dept_name依赖dept_id,而dept_id依赖emp_id,这样就存在了传递依赖。而且我们可以看出传递依赖的一个明显缺点就是数据冗余非常严重。

  那么如何解决传递依赖问题,其实非常简单,我们只需要将dept_name,dept_info这连个属性删除就可以了,即Employee(emp_id,emp_name,emp_age,dept_id),然后再创建一个部门表Dept(dept_id,dept_name,dept_info)。这样如果要搜索某一个员工的部门信息dept_info,可以通过数据库连接来实现,查询语句如下:

select e.emp_id,e.emp_name,d.dept_name from Employee e,Dept d where e.dept_id=d.dept_id

BC范式

  设关系模式R<U,F>∈1NF,如果对于R的每个函数依赖X→Y,若Y不属于X,则X必含有候选码,那么R∈BCNF。
  解释一下:对于关系模式R,若 R中的所有非平凡的、完全的函数依赖的决定因素是码,则R属于BCNF。
  若R∈BCNF
  每一个决定属性集(因素)都包含(候选)码
  R中的所有属性(主,非主属性)都完全函数依赖于码
  R∈3NF(证明)
  若R∈3NF 则 R不一定∈BCNF
  在关系模式STJ(S,T,J)中,S表示学生,T表示教师,J表示课程。
  每一教师只教一门课。每门课由一名教师教,某一学生选定某门课,就确定了一个固定的教师。某个学生选修某个教师的课就确定了所选课的名称 : (S,J)→T,(S,T)→J,T→J
  由关系模式的定义可以得到如下结论,若R属于BCNF,则R有:
  1.所有非主属性对每一个码都是完全函数依赖。
  2.所有的主属性对每一个不包含它的码,也是完全函数依赖。
  3.没有任何属性完全函数依赖于非码的任何一组属性。
  由于R∈BCNF,按定义排除了任何属性对码的传递依赖与部分依赖,所以R∈3NF。但是若R∈3NF,则R未必属于BCNF。
1.第一范式:数据库的字段是单一属性,不可再分。
 解释:
  • 不能是复合属性,如果存在,应该拆分为多个属性
  • 不能是多值属性,如果存在,应该建立一个实体,而让此属性与其存在1对多的关系)
  • 不能是重复属性
2.第二范式:任何非关键字段不能部分依赖任一侯选关键字(即必须完全依赖)
 解释:
  • 表中必须存在侯选关键字,即每一行不同于其他任一行,是惟一区分的
  • 任何非关键字段不能依赖于侯选关键字的一部分
3.第三范式:任何非关键字段不能传递依赖任一侯选关键字
 解释:
  • 非关键字字段必须直接依赖任一侯选关键字
  • 非关键字段C不能依赖非侯选关键字B,因为样会形成传递依赖:侯选关键字A=>B=>C,因为这时的B往往是外键,即其他表的主键,也就是说表中不能含有其他表的非主属性
4.BC范式:任何字段都不能传递依赖任一侯选关键字
解释:
  • 与第三范式相比,一个是“任何非关键字段不能”,一个是“任何字段不能”,显然更严格了
  • 侯选关键字或其部分字段不能传递依赖其他的侯选关关键字
注释:
侯选关键字:又叫侯选码,惟一标识一行数据,其真子集不能是侯选关键字,一个表可以存在多个侯选关键字,如用户表的username,userid
主关键字:又叫主键,主码,被选中的用来区分其它行的侯选关键字,一个表只有一个主关键字
部分依赖:(A,B)->C,D,如A->C,则C部分依赖A
传递依赖:A->B->C,则C传递依赖A

注意点:

  1. 数据库连接会带来一部分的性能损失
  2. 并不是数据库范式越高越好
  3. 有时会在数据冗余与范式之间做出权衡,在实际的数据库开发过程中,往往会允许一部分的数据冗余来减少数据库连接。

有关范式与反范式的讨论

责任编辑:晓熊作者:cnblogs   2008-11-03   
文本Tag: 数据库

【IT168技术分析评论】
  我个人很喜欢反范式这种应用,每次数据库设计用到时心里都有点欣喜,或许是因为它之间夹杂着那么点美感吧。时而提醒我计算机不仅仅是一门科学,也是一门艺术。We are not just Coder. We are all Designer! 不是吗?谢谢Dylan Tang带来的的好文章。下面通过Dylan的文章来侧重讨论一下反范式的使用,同时也谈了点memcached使用的个人想法。首先申明,对于Dylan的最终方案我是赞同的。

  反范式确实违反了三个范式的关系数据理论,那我们为什么不做个乖孩子而要去违反范式设计理论呢?下面从Dylan举的那个例子来说起什么场景下应该使用反范式。假如我们现在要查询大众点评网社区里面最新的10个帖子,如果按照范式设计,那么可能要关联两个表,一张是帖子表,另外一张是会员表,整个查询如下:

  SELECT TOP 10 N.帖子标题, U.会员昵称,N.会员ID FROM 帖子表 N

  JOIN 会员表 U

  ON N.会员ID=U.会员ID

  ORDER BY N.帖子ID DESC

  上面的sample如果你懒的仔细看或者没怎么看懂,那么我再给你简单解释一下,讲的就是当我拿出来的10个最新帖子以后,在显示这几个帖子的时候我也要显示会员的昵称,但是帖子那张表没有昵称,只要再次到会员表拿用户昵称了,所以这里就要涉及到两张表了。常用的方法就是像上面sql语句一样用一个自然连接查询来搞定。作为一个solution,这个方案挺好。如果拿出10个新帖子,然后根据每个帖子的会员ID再去会员表拿会员昵称,因为会员ID是加了index的,性能应该还不错。但是Dylan gg嫌这个还是太慢了。当我们的业务逻辑涉及的表要在百万数量级以上时候,当我们在写sql语句时候就一定要小心了,慢点的操作可能几个小时结果都出不来。特别是遇到像join, order, group by, distinct这样的操作,一定要小心一点。这时对算法的设计要求还是挺高的。但实际上上面这个查询应该是挺快的,所以说这个sample举的不大好,虽然可以很清楚让人明白而且它还是个实际的问题,但是它对性能提升的要求还不够强烈。我们姑且假设用这个join慢的如老牛吧,那我们该怎么办?

  这不,Dylan Tang提出了第二种解决方案,这次是利用反范式,在帖子表里面添加冗余字段——会员昵称,这样我们就可以通过下面的查询达到同样的目的:

  SELECT TOP 10 帖子标题, 会员昵称,会员ID FROM 帖子表 ORDER BY 帖子ID DESC

  这个方案不错,把反范式的大概使用场景说出来了。反范式在这种场景下,对于boost performance忒有用。你可以避免连接两张表才就完成了这种要经常操作的查询。仅仅通过一张表就可以搞定,这对于性能提升是很显著的。但是Dylan还是不大满意,他说,”每当一个会员去更新自己的昵称的时候,我们会执行一个存储过程,这个存储过程的目的就是去更新大量的会员昵称的冗余字段,这些更新对于一个活跃会员来说,将是非常耗时的,因为需要更新的数据实在太多,而Web 2.0的精髓之一就是个性化,这样的设计对于个性化来说,有着不可调和的矛盾”,这个理由讲的很清楚。他担心的不是一致性,而是维护一致性需要block修改昵称用户太长时间。这里我们是不是可以深入的讨论一下,有没有其它的方案来解决这个问题?我们是不是可以有选择性的来同步更新影响比较大的冗余字段,其它的就可以通过异步操作来解决。这样我们就不会长时间blocked住修改昵称的用户。实际上这里是表现了一个经典的理论就是Dylan希望通过类似采用事务一样的ACID(Atomicity, Consistency, Isolation, Durability)来保证一致性,但是对于高可用架构网站的架构师更多的考虑应该是采用BASE(Basically Available, Soft-state, Eventual Consistency)吧。BASE 策略是 Inktomi 公司的 Eric A. Brewer 在 1988 年提出的。更多关于BASE请查看文章后面的参考文章。可以通过暂时的不一致来保证用户的可用性,这对于用户来说也是基本可以接受的,对吧。所以这里的反范式方案的Core Problem也从作者遇到的事务操作的可用性问题转变成反范式带来的潜在的不一致风险问题。

  为了对比,我说一个新的的反范式使用场景,就举一个我们都熟悉的场景,你现在已经可以看到我这篇文章有多少人阅读了,在博客园后台服务器上有个记录我这篇文章信息的数据库表,这个表里面可能有个字段来统计阅读人数。这时候同样会遇到上面说的问题,当你读我这篇文章的时候,是不是应该在那个字段上加1然后再返回给你我这篇文章内容的响应?我想博客园也同样是屈服于BASE而放弃ACID。它通过ajax来发异步request到一台新的域名服务器上来统计阅读数,它后台可能采用延迟写来避免高频度的写操作。只有当阅读计数器到一定阀值或者每隔x分钟后,才将内存中这个计数flush到那个字段里。这样就可以减轻反范式可能遇到的频繁写操作。我想延迟写算是最常用的性能调优的方法之一吧,提前读也算一种,google ditu里面就充分应用了。

  这里插一个问题,为什么cnblogs要用ajax来记录阅读数?关于这个话题,你还可以参考这篇文章<< 网站日志收集方式简介>>。使用这种方式的确有很多优点,首先新的域名是的requests可以突破常用浏览器同一域名的并发数的限制,其次可以使得这种记log操作和后台逻辑脱离开来,类似AOP的思想。再者也可以在爬虫访问时不会被统计,还有就是这种方式容易scale out,可以放在专门的机器上来做这种统计,因为统计的计算比较简单,所以主要遇到的问题还是I/O瓶颈。

  遇到上述这个I/O瓶颈怎么办?加新的机器来分摊I/O操作?我们先可以延缓这个加机器的欲望,而采用延迟写来减少一点运营成本。就是我上面说的,当阅读计数器到一定阀值或者每隔x分钟后,才将内存中这个计数flush到统计阅读数那个字段中。你可以自己实现内存管理或者使用memcached等软件来帮助你来管理阅读计数器的存储,但由于memcached重启或者其它操作可能使得内存中的那些缓冲会丢掉,这里你就需要自己来选择符合你需求的方案了。对于memcached你可以在每次重启前先把这些数据flush到DB中。这个方案对统计阅读数应该是可以接受的,毕竟memcached的重启不会那么频繁吧,而且如果阀值不是太高,即使丢了某些数值,还是能够接受的。如果能够给memcached加上persistent功能就可以轻松搞定这个问题了,国内sina做了一个开源的项目Memcachedb,感兴趣可以了解一下,它是结合BDB做的。Sina的blog的访问数好像就这么做的,也佐证了我这个想法。总之,如果我们能够接受带来的不一致风险的话,可以考虑采用延迟写来提升性能。

  现在你对反范式使用优缺点应该有更为清晰的了解了吧。小结一下,反范式主要问题是采用ACID时候可用性有问题,而采用BASE时候,可能导致冗余字段的不一致性。优点当然是能够避免很多实时计算来提高性能。后台还可能有这么一个表来详细记录哪些用户看了我这篇文章,它的用途可以是避免重复记录阅读数来保证阅读数的准确,这估计也要记录阅读者的IP吧,不然很难防止用户作弊,同时也可以为网站的数据挖掘提供点基础数据。如果要统计我的文章被阅读次数每次都从这个阅读者记录表重新统计一次,这成本根本没有办法接受,没有人会傻的这么干的。依赖于反范式可以很好解决这个问题,它仅仅通过一点的存储成本就节省了很多的计算,把统计需要的计算细分到每个用户访问时进行的。这里也有可能ajax的http request发送失败。同样,这里可能会出现结果最终不一致。但是我们并不care,丢一个两个没关系。所以我想说反范式某些场景很有用的。Dylan使用反范式的遇到问题你知道怎么克服了吗?什么?你还不知道,我要打你pp。哎,那我再说一遍,选择BASE, not ACID。记住了。我们可以异步更新帖子表那个字段,名字丢失概率应该很小很小,而且我不知道有几个人闲着没事干天天改名字。同时对于这种想保证结果的一致性,你可以在后台有专门的服务来验证表之间的一致性。我想我们是不是应该避免使用外键,避免使用存储过程,数据库表的一致性由外围服务来保证。这应该也是未来的计算模式吧。

  由于这一篇文章是想向大家推荐反范式的应用,所以写的比较多。希望没有思考过这种方案的同志们可以正视这个解决方案。偶真的真的很喜欢她,希望你的审美观和我类似。

  下面继续分析分析那篇文章,看看作者提出的”王者归来” 解决方案。他的意思是把帖子先拿出来,然后看每个帖子的会员号,然后看他们memcached里面有没有,有就直接拿不需要通过db,没有就需要从DB取,然后塞入memcached,同时得到会员昵称。这个方案挺好,很容易理解。这个方案的确是比那个反范式方案完美的多。我承认这一点。特别是对于涉及会员表的这个应用很合适,为什么说呢,因为用户表是Identity系统以及整个网站应用的核心表,使用很频繁,很多应用都得依赖于用户基本信息。但是我对于他所说的,” 通过缓存系统封装的批量读取的方法得到这些会员的昵称,再显示到网页上”有点疑惑,这个”批量”是什么意思?这对于memcached的命中率应该是影响不小吧,难道是单独用个memcached机器来干这个。我说说我思考的两种方案。第一,如果内存比较紧张的话,可以把最近活跃的用户放在memcached,采用类似LRU的算法,这应该是memcached内存利用很好的方案吧。第二,点评网应该有不少收入了,有银子当然要多买点机器了,那内存就不是问题了。那么我的想法是把所有用户的基本信息load到memcached中,这样就不存在到底该把哪些用户信息放到memcached中的问题了。这时候设计到用户操作都可以飞快,降低了I/O的负载。点评网的餐馆等信息也可以这样干,当然关于memcached的更新策略问题也就出现了,不过还算比较简单的。我来大概把整个用户表放到内存中需要的大概内存量,假设是500万用户,打算放用户ID,假设是个int, 4个字节,用户昵称假设16个字节,汉字得看编码情况了,英文平均8个字母绰绰有余吧,你可以统计一下长度然后调整为更为合适的数值。主要用到的就是这两个字段吧,其它的看情况再加上把。(4+16)*500*10^4bytes/(1024*1024)结果大约95M左右。才用了这么点内存。。。。。。

  最后,为了检验一下大家是不是还记得清楚三大范式的定义和统计一下有多人认真看完了这篇文章,请大家跟贴说出你对三个范式的理解。

  作者:刘守照

  出处:http://liushouzhao.cnblogs.com

数据库ACID 理论

分类: 数据库 2306人阅读 评论(0) 收藏 举报
数据库数据结构logging程序开发工作

目录(?)[+]

 

ACID,是指在数据库管理系统(DBMS)中事务所具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

在数据库系统中,一个事务是指由一系列数据库操作组成的一个完整的逻辑过程。例如银行转帐,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和构成一个完整的逻辑过程,不可拆分。这个过程被称为一个事务,具有ACID特性。

具体举例:

   设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:  
  · 更新客户所购商品的库存信息   
  · 保存客户付款信息--可能包括与银行系统的交互   
  · 生成订单并且保存到数据库中   
  · 更新用户相关信息,例如购物数量等等   

   正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。  

  数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。 

 

四大特性

      · 原子性
  事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。  

  · 一致性  

   事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。  

  · 隔离性  

  由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。   

  · 持久性  

   事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。   

实现

由于一项操作通常会包含许多子操作,而这些子操作可能会因为硬件的损坏或其他因素产生问题,要正确实现ACID并不容易。ACID建议数据库将所有需要更新以及修改的资料一次操作完毕,但实际上并不可行。

目前主要有两种方式实现ACID:第一种是Write ahead logging,也就是日志式的方式。第二种是Shadow paging。