3 万字,关系型数据库性能体系,设计和效率提升

Posted Lucifer三思而后行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3 万字,关系型数据库性能体系,设计和效率提升相关的知识,希望对你有一定的参考价值。

目录

本文已经作者授权发布
作者: 数据治理体系 公众号: 数据治理体系

前言

本文档详细定义了关系型数据库的:

  • 数据库模型设计规范
  • 表的设计规范
  • 分区表的设计规范
  • 索引的设计规范
  • 其他数据库对象的设计规范
  • SQL的访问规范
  • 编码和注释规范

并详细介绍了 SQL 调优 的关注点和常用方法,作为数据库规划、设计、开发及维护人员的技术参考资料。

用以指导关系型数据库的设计和开发,突出性能是设计出来的,证明质量也是可以设计出来的。

只要理解透这篇,关系型数据库性能提升 30% 不是问题!

一、预期的读者和阅读建议

本文档预期的读者:

  • 项目经理
  • 开发经理
  • DBA
  • 数据结构管理师
  • 系统设计师
  • 开发师
  • 测试师

等相关岗位的人员。

读者可以通篇阅读该文档,以整体熟悉和掌握 Oracle 数据库设计规范,也可以重点关注跟自身相关的内容章节。如数据库模型设计、表的设计,或 SQL 访问规范、编码和注释规范等。

二、数据库模型设计规范

1、数据库建模原则性规范

对于涉及数据库的项目,需要构建数据库逻辑模型图,逻辑模型图是项目组成员之间在数据库层面沟通交互的依据,必须规范画图(表,主键,外键,关系)。

对于表的个数在 20 个以上的模型,需要 DBA 参与设计,并作最终审核。

对于 OLTP 系统,采用范式化思想进行模型设计,对于OLAP 系统,采用面向问题及多级颗粒度的思想进行模型设计。

需采用主流的模型设计软件工具 PowerDesigner,ERWin

2、实体型之间关系认定规范

所有实体间的业务逻辑关系,除了语义上保留其原有的业务关系外,本质上都要转化成关系数据库的三种关系(1:1)(1:N)(N:M),对于 3 个及以上实体型之间的“多元关系”,需要 DBA 参与设计。

比如,实体型 A 和实体型 B 之间的关系,可以通过问两个问题来确定他们之间的关系:一个 A 可以对应几个 B ?一个 B 可以对应几个 A ?

  • 一个A对应一个B,相反一个B对应一个A,那么A对B就是1:1关系;
  • 一个A对应多个B,相反一个B对应一个A,那么A对B就是1:N关系;
  • 一个A对应多个B,相反一个B对应对个A,那么A对B就是N:M关系;

具体实施的时候,掌握如下原则:

  • 1:1 关系选取任何一个表的主键到另一个表中,作为外键来体现。

  • 1:N 关系将1表的主键在N表中,以外键形式来体现。

  • N:M 关系采用“关系表”来体现,该关系表的主键是由相关实体表的主键组成的复合主键;各实体表主键不但组成了该关系表的主键,同时也被看作外键在该关系表中存在。

  • 对于三个以上表之间的“多元关系”常需要和反范式化冗余字段结合起来设计,以保证查询速度。

3、范式化 1NF 的规范

OLTP 系统的模型,需要符合第三范式,对于表在 20 个以上的模型,需要 DBA 参与设计。

范式化要求(1NF):列是访问的最小单位,具有原子性,不可再被分割。

在具体实施的时候,需要依据情况对相应属性进行拆分或者合并:

  • 同一个属性值的不同细度把握

比如,常见的“姓名”这个属性,设计一:“姓名”是一个列,设计二:“姓”是一个列,“名”是一个列,两个列的值组合起来才表达一个“姓名”语义。两种设计方法,在不同的系统中都有应用,这主要是依据需求的细度来确定,灵活把握。

  • 把多个属性值错误的作为一个属性值存储

比如:常见的OA系统要存储员工的各种属性,包括技能信息,技能范围:Oracle,JAVA,.NET,C#,Perl,UNIX等等,一种常见的错误设计是:设计一张员工表,其中有一个技能属性字段,然后某员工所掌握的多种技能用逗号 , 间隔,然后将这个字符串存储到这个员工表的技能属性字段中。

这里的错误在于将多个属性值作为一个属性值存储在一个字段中,不能满足直接遍历员工对某个技能掌握情况,而且如果再要求说明员工对个技能的掌握程度(精通,熟悉,一般等等),则再增加字段,里面的对应关系将很容易错乱,这是严重违反 1NF 的情况。

正确的设计 应该是:两个实体表:一张是员工表,一张是技能字典表,一个员工可以掌握多个技能,也就是(1:N)关系,相反一个技能可以被多个员工掌握,也是(1:N)关系,双向都是(1:N)关系,那么综合起来员工和技能之间就是“多对多关系(N:M)”,依据前述规范,应该设计一张“关系表”来存储“多对多关系”,主键为复合主键(员工主键+技能主键),该关系有一个属性“技能掌握程度”。

4、范式化 2NF 的规范

OLTP 系统的模型,需要符合第三范式。对于表在 20 个以上的模型,需要 DBA 参与设计。

范式化要求(2NF):满足 1NF,不存在非主键属性对主键属性的部分依赖。

实体表中一般不会出现违反 2NF 的情况,因为都是“一个”主键列,而关系表是两个以上列的“复合”主键,故而关系表容易出现违反 2NF 的情况。主要是该关系表非主键外的属性,本该属于相关的某个实体表的,却放到了该关系表中。这使得该属性不能通过该关系表的复合主键唯一确定, DML 操作会发生错误。

如果违反了 2NF,那么应该把这个属性从关系表中拆分,也许会单独形成一个表,绝大部分情况下是将该属性归并到某个相关的实体表中。

违反 2NF 的例子:

学生考试情况中,有两个实体表:学生表和学科表,学生与学科之间的考试关系就是 N:M 的关系,就要创建一张关系表存储该多对多的考试关系,表的主键为学生编号和学科编号,属性为考试分数;那么“任课老师”该放在那里呢?

如果放到考试关系表中,那么安排任课老师,必须先进行考试,这显然不符合实际,也就是任课老师不该依赖于学生编号和学科编号,只是依赖于学科编号,也就是说任课教师信息应该放在学科表中。

5、范式化3NF的规范

OLTP 系统的模型,需要符合第三范式。对于表在 20 个以上的模型,需要 DBA 参与设计。

范式化要求(3NF):满足 2NF,不存在非主键属性对主键属性的传递依赖;

违反 3NF 的情况,绝大多数是在含有外键的表中。比如 A 表中的外键字段 Bkey 是 B 的主键,那么依赖于 Bkey 的属性应当属于 B 表的属性,而不是 A 表,如果放入 A 表,则这些对 A 表的主键 Akey 的依赖,首先是依赖于 A(BKey),而后通过 A(BKey) 对 A(AKey)的依赖,传递依赖于 A(Akey);三种关系(1:1,1:N,N:M)都含有外键,都很可能发生违反 3NF 的情况。

违反 3NF 的后果,会导致那些问题属性插入异常,或者被误删。

违反 3NF 的例子:

教师和学科之间,存在着上课关系,假设一个教师上一门课而且一门课只有一个教师上,那么该关系为1:1关系,将教师表的主键教师编号在学科表中以外键形式存在就表达了该1:1关系,那么教师的“联系电话”属性该放哪里呢?

如果看到“教师编号”出现在了学科表中,就将联系电话放入学科表中,那么联系电话首先是对表中的教师编号依赖,再依据教师编号对学科的依赖,达到了学科编号的依赖,那么联系电话对学科编号的依赖就是传递依赖,违反了 3NF。

应该将其从学科表中拆出来放入教师表中,不然的话,会发生操作异常,比如,假设一个教师已经存在但是还没有为其分配科目,那么他的电话就无法存入库中。

6、反范式化冗余字段使用规范

OLTP 系统中在完成范式化工作之后,对某些表,可以适当反范式化增加冗余字段以提高数据访问性能;在 OLAP 中采用的是面向问题的设计思想,应该大量使用反范式化冗余信息。

当SQL关连查询涉及到4张表时可考虑采用冗余字段。

常用在两个地方:

  • 关系表中的冗余:在关系表中增加相关实体表的相关属性,以达到关连查询时减少表的关联数量的目的;
  • 层次关系中的冗余:在多层次的子父表关系中,将父表的属性存储在“子表”或者“孙子表”或者“重孙表”中。

反范式化冗余字段实例:

  • 关系表中的冗余:比如在考试关系中,原本在学科表中的学分信息,可以冗余添加到考试关系表中,这样,每个学生得了多少学分,就可以直接从考试表得到,而无需关联学科表来得到。
  • 多层关系中的冗余:假设为之范畴从大到小有国家表,省份表,城市表,城区表,社区表,它们之间的层次关系是通过上一级的主键在下一级中以外键形式存在来体现的,但是,如果需要问:某个设计属于哪个国家?这样就要关连查询所有的5张表,性能会很差。这时可以将国家编号以外键形式放入到社区表中做冗余,这样直接关联国家表和社区表即可得到答案。一般的,每间隔一级增加一个冗余外键,比如将国家编号放入城市表中,将城市编号放入社区表中。

如何保证冗余字段数据的正确性(一致性)是反范式化的关键,需要对冗余字段详细添加注释,说明冗余了什么,以及该字段的维护方法,常用维护方法如下:

  • 如果在程序开发前设计的冗余字段,可以在正常的业务逻辑程序中一并处理;
  • 如果是程序完成之后增加的冗余字段,可以使用触发器维护;
  • 对于OLAP中大量存在冗余字段,可能需要使用单独的处理任务进行维护。

7、数据库对象命名基本规范

7.1、遵循行业规范

当有相关国家/行业强制性数据结构标准规范存在时,用于存储某业务数据的业务表在表名命名上原则上应该遵从标准规定,其表中相关字段的中文名称(即数据项名称)若标准规范上有规定的应遵循规定。

此外,若标准规范上对数据项的类型、长度有规定的,原则上也应当遵循或保证能直接兼容保存和访问。

7.2、简单命名原则

命名尽可能简单,避免太长的命名,尽量使用缩写形式,但是缩写也要能够表达命名的含义。凡是需要命名的对象其标识符均不能超过 30 个字符,也即:Oracle中的表名、字段名,函数名,过程名,触发器名,序列名,视图名的长度均不能超过 30 个字符,以免超过数据库命名长度限制(Oracle有30的限制)。

建议每个单词分段长度不要超过6位。

7.3、字符范围原则

数据库各种名称必须以字母开头,但严禁使用 SYS 开头;名称只能含有字母,数字和下划线“”三类字符,“”用于间隔名称中的各语义字段,以便阅读同时方便某些工具对数据库对象的映射。

如XXX_XXX_XXX,但不限于三段式。

7.4、字母全部大写或小写原则

所有数据库对象命名字母全部大写或小写。

Oracle对大小写不敏感,但是有些数据库对大小写敏感,统一大小写有助于在多个数据库间移植。

7.5、勿用保留词原则

数据库对象命名不能直接使用数据库保留关键字,但分段中可以使用。如 USER 不能用于表名、列名等,但是 USER_NAME 可以用于列名,USER_INFO 也可以用于表名。

7.6、同义性原则

对于同一含义尽量使用相同的单词命名,不管使用英文单词还是英文缩写,以免引起误解。

如TELEPNHOE的A表中表示固定电话号码,在B表中就不应该用于表示移动电话号码。尽量避免同一单词表示多种含义的情况。

7.7、富有含义原则

命名尽量采用富有意义的英文词汇,不准采用汉语拼音。

7.8、扩展性原则

各系统或者项目在遵循本规范的基础上可以根据需要制定更明确的规范细则,以满足项目管理需要。

如对模块进行统一命名,然后用于表名的前缀。建议每个系统在启动开发时建立数据字典,管理命名中使用的英文单词、英文单词缩写等,对用于命名的单词进行统一管理。

三、表的设计规范

1、命名规范

1.1、表的命名规范

命名规则: 3位类别码_模块名_表名_附加码,采用大写字符

类别码: 一般表 TBL、临时表 TMP、中间表 CVT、删除表 DEL、历史表 HIS、配置表 CFG,接口表 INT,一般表的 3 位类别码可以省略,其他类型表的类别码必填。

模块名: 模块名代表子系统(或者子模块)的名称,如:保单相关表 PLC;订单相关SLS;基础数据:TYP。

表名: 表名应该简洁明了,尽量使用完整的单词,如果导致拼上表名后,长度超过 30 个字符,则从最后一个单词开始,依次向前采用该单词的缩写。(如果没有约定的缩写,则采用该单词前 4 个字母来表示)。另外,表名中的名词单词都应使用单数形式,以免混淆,如:使用 FACTORY 而非 FACTORIES。

附加码: 为可选项,各系统根据实际情况自行编码,如:可以用以标记临时表的生成及数据存放日期YYMMDD。

1.2、字段的命名规范

命名规则: 英文单词之间用下划线连结,且每个单词皆为单数。例:user_name,采用小写字符

  • 字段用来存储 sequence 序列,命名以id结尾。例:bar_code_id。
  • 字段用来存储号码,命名以 no 结尾。例:policy_no。
  • 字段用来存储日期,命名以 date 结尾。例:create_date。
  • 字段用来存储数量,命名以 num 结尾。例:insured_num。
  • 字段用来存储金额,命名以 amt 结尾。例:prem_amt。
  • 字段用来存储名称,命名以 name 结尾。例:client_name。
  • 字段用来存储描述信息,命名以 desc 结尾。例:bank_desc。
  • 字段用来存储基础表的 code 信息,命名以 code 结尾。例:region_code。
  • 字段用来存储标志信息,命名以 flag 结尾。例:underwrit_flag。
  • 字段用来存储英文名称和英文描述,命名以 en 结尾。例:address_en。

2、表的设计规范

2.1、指定表空间规范

每个表在创建时候,必须指定所在的表空间,不要采用默认表空间,以防止表建立在 system 空间上,导致性能问题。

对于事务比较繁忙的数据表,必须存放在在该表专用空间中。

2.2、表的主键规范

表的主键设计,应该遵循如下三点原则:

  • 有无原则

除临时表和外部表,以及流水表,日志表外,其他表都要建立主键。主键是每行数据的唯一标识,保证主键不可随意更新修改,在不知道是否需要主键的时候,请加上主键,它会为你的程序以及将来查找数据中的错误等等,提供一定的帮助。

  • 构成原则

主键不能使用含有实际语义的列,应该增加一个 xx_id 字段做主键,类型为 number,取值来自序列 sequence;

  • 创建原则

对于500万以上的表,采用先建唯一索引再添加主键约束的方式来创建主键。

对于实体表,主键就是一列,就是没有任何语义的自增的 NUMBER 列;对于关系表,主键就是相关实体表主键形成的复合主键,是多列。

2.3、表的外键规范

一个表的某列与另一表有关联关系的时候,如果加得上的话,请加上外键约束。外键是很重要的,所以要特别强调。

  • 适量建立外键

为了保证外键的一致性,数据库会增加一些开销,如果有确凿的并且是对性能影响到无法满足用户需求的证据,可以考虑不建外键。否则,还是应该建外键。

  • 不要以数据操作不方便为理由而不建外键

是的,加上外键以后,一些数据操作变得有些麻烦,但是这正是对数据一致性的保护。正是因为这种保护很有效,所以最好不要拒绝它。

  • 以缺省的方式建立外键

以缺省的方式建立外键(即用delete restrict方式),以达到保护数据一致性的目的;外键在保护数据一致方面非常有效。如果不建外键,数据库中容易出现垃圾数据,并且无人知晓。当数据量很大的时候,查找这些垃圾数据也是相当困难的。而应用程序在设计时,往往没有考虑或者也无法照顾到垃圾数据。因此垃圾数据很可能造成应用程序工作不正常,并且表现出来的现象会很奇怪,让人摸不着头脑。

2.4、字段类型及宽度的规范

字段的宽度要在一定时间内足够用,但也不要过宽,占用过多的存储空间,对于长度不确定的列,采用可变长度的数据类型如 varchar 类型;

字段的类型及宽度在设计以及后面进行开发时,往往要与应用的设计、开发人员商讨,以得到双方认可的类型及宽度;

2.5、一个表所含字段总长度的规范

一个表中的所有字段,应当能存储在一个数据块中(BLOCK),也即:表的单行字段总长度 < db_block(减去pctfree)。对不含有大对象数据类型字段的表,字段数大于 50 个的,请 DBA 团队参与设计。

查询字典表 USER_TAB_COLUMNS 中的字段 DATA_LENGTH 得到表中所有字段的总长度,再依据 db_block 和表的 pctfree 参数可以判断是否一个数据行可以存储在一个数据块(BLOCK)中。

对表添如果所有字段的总长度超出了一个数据块,那么需要将该表拆分成两个(甚至多个)表,拆分的依据是字段的频繁使用程度,也就是频繁使用的字段在一个表中,很少被使用的字段放在另一个表中,他们之间使用相同的主键值,用主外键关联。这点就是“一个表所含字段访问频繁度的规范”。

2.6、一个表所含字段访问频繁度的规范

一个表中的各字段的访问频繁度应该基本一致,如果一个表的字段数超过50个, 请DBA参与审核。

如果一个表的字段数过多超过 50 个,并且依据业务逻辑确定该表中一些字段频繁被访问,另一些字段则很少被访问,则该表需要做拆分处理,这样可以避免读取频繁信息时多读取很少被访问的信息,可以提高 IO 性能,减少内存耗费,这在 OLAP 系统中比较常见。

将访问频繁度相差太远的字段拆分到两个表中,一个表存频繁访问的字段,另一个表存很少被访问的字段。

2.7、大对象字段(BLOB,CLOB)使用规范

存储图片,视频,音频,文件,500字节以上文本等占用太多空间的字段(大对象字段),不能和其他字段存储在一个表中。含有大对象(BLOB,CLOB)字段的表设计和存储请DBA参与设计。一般有两种方法:

  • 数据库存储

可以重新建一个表专门存储该大对象字段,该表基本为两个字段,一个为大对象编号 ID 为主键,一个为大对象内容本身,并将该主键在原表中作外键关联,该大对象表存储在单独的表空间中。

  • 操作系统存储

将这些文件存储在操作系统空间中,大对象字段存储该文件的全路径名。

如果该大对象字段常被修改,那么采用方法一;如果该大对象信息为静态,加载后基本不变,那么可以采用方法二,它有一个致命缺点就是信息存储在数据库外部,不安全,容易丢失。

2.8、关于字段能否为 NULL 值

对于字段能否为 null,应该在 sql 建表脚本中明确指定,不应该使用缺省。由于 null 值在参加任何计算时,结果均为 null,所以在程序中必须用 nvl() 函数把可能为 null 值的字段或变量转换 非null 的默认值。

2.9、关于冗余列的规范

除非必要,否则尽量不加冗余列。

所谓冗余列,是指能通过其他列计算出来的列,或者是与某列表达同一含义的列,或者是从其他表复制过来的列等等。冗余列需要应用程序来维护一致性,相关列的值改变的时候,冗余列也需要随之修改,而这一规则未必所有人都知道,就有可能因此发生不一致的情况。

如果是应用的特殊需要,或者是为了优化某些逻辑很复杂的查询等操作,可以加冗余列。

2.10、使用注释的规范

每个表,每个字段都要有注释,说明其含义,对于冗余字段还要特别说明其维护方法,外键字段说明参照与那个表。原则上谁设计谁注释。

查询字典表 user_tab_comments 和 user_col_comments 可知道表和字段的注释信息。

对表添加注释:

SQL>comment on  table  <table_name> is 'xx';

对字段添加注释:

SQL>comment on column<table_name>.<col_name> is 'xx';

2.11、一个表所含数据量的规范

一个非分区表中的数据量不要超过 500 万。 当一个非分区表中的数据量超过 500 万时,需设计成 分区表 ;如果该表数据量超过 5000 万,请 DBA 参与设计。

在系统上线前: 通过对业务分析,判断一个表的数据量;

在系统上线后: 可以通过 exp 的日志,Top 性能 SQL,count(1) 来发现数据量大的表。

将这些表进行分区,具体方法请参看分区表的设计规范。

记录数超过两亿条的表一定要考虑信息生命周期,必须考虑历史数据的剥离,并在应用设计中完成对历史数据的相应处理功能(历史数据的剥离规则须经业务使用部门的确认)。

2.12、增量同步表的设计规范

字典信息表和需要使用增量同步的表必须增加如下属性。

属性名类型取值说明
StatusChar(1)Y/N:Y为激活N为作废,默认为Y标识该行是否使用。用于软删除,软删除需将主键和唯一约束列添加随机数后缀。
Create_timeDate默认为sysdate创建时间
Update_timeDate默认为sysdate最后修改时间

3、字段类型规范

3.1、不使用会发生隐式转换:INTEGER,FLOAT

INTEGER 改为 NUMBER(n)

FLOAT 改为 NUMBER(p,s)

3.2、不使用过时老类型:RAW,LONG,LONG RAW

非标准: VARCHAR2(n CHAR)、CHAR(n CHAR)

VARCHAR2(n CHAR) 改为 VARCHAR2(n)

CHAR(n CHAR) 改为 CHAR(n)

3.3、国家字符集相关

国家字符集相关: NCHAR,NVARCHAR2,NCLOB

NCHAR 改为 CHAR

NVARCHAR2 改为 VARCHAR2

NCLOB 改为 CLOB

3.4、不能使用大对象:BLOB,CLOB,NCLOB

不能使用大对象: BLOB,CLOB,NCLOB

CLOB 和 NCLOB 改为 VARCHAR2

3.5、不能使用高精度:TIMESTAMP

不能使用高精度: TIMESTAMP

TIMESTAMP 改为 DATE

3.6、关于 CHAR 字段

CHAR 字段类型长度小于 100,长度大于 100 的字符型信息应该使用 VARCHAR2 字段类型来存储。

四、分区表的设计规范

1、表空间及分区表的概念

1.1、表空间

是一个或多个数据文件的集合,所有的数据对象都存放在指定的表空间中,但主要存放的是表,所以称作表空间。

1.2、分区表

当表中的数据量不断增大,查询数据的速度就会变慢,应用程序的性能就会下降,这时就应该考虑对表进行分区。

表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据在物理上存放到多个“表空间”(物理文件上),这样查询数据时,不至于每次都扫描整张表而只是从当前的分区查到所要的数据大提高了数据查询的速度。

2、表分区的具体作用

Oracle 的表分区功能通过改善可管理性、性能和可用性,从而为各式应用程序带来了极大的好处。

通常,分区可以使某些查询以及维护操作的性能大大提高。此外,分区还可以极大简化常见的管理任务,分区是构建千兆字节数据系统或超高可用性系统的关键工具。

分区功能能够将表、索引或索引组织表进一步细分为段,这些数据库对象的段叫做分区。每个分区有自己的名称,还可以选择自己的存储特性。从数据库管理员的角度来看,一个分区后的对象具有多个段,这些段既可进行集体管理,也可单独管理,这就使数据库管理员在管理分区后的对象时有相当大的灵活性。

但是,从应用程序的角度来看,分区后的表与非分区表完全相同,使用 SQL DML 命令访问分区后的表时,无需任何修改。

什么时候使用分区表:

  • 表的大小超过 2GB,数据量超过 500 万;
  • 表中包含历史数据,新的数据被增加都新的分区中。

3、表分区的优缺点

表分区有以下优点:

  • 改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度。
  • 增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用;
  • 维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可;
  • 均衡I/O:可以把不同的分区映射到磁盘以平衡I/O,改善整个系统性能。

缺点:

  • 已经存在的表没有方法可以直接转化为分区表。不过, Oracle 提供了 在线重定义表 的功能。

4、分区表设计规范

4.1、不使用全局索引

在分区表中不建议使用 全局索引,因为 truncate 分区时会导致全局索引失效,造成难以维护。

4.2、RANGE 分区的规范

大数据量的表需进行分区化,当表的数据量超过 500 万,需设计成分区表,当表的数据量超过 5000 万,请 DBA 参与设计。

SQL 常依据某列的范围访问表,则对表使用 RNAGE 分区。常见情况是 SQL 根据 时间范围 进行查询,则使用 时间字段 作为分区关键字进行 RANGE 分区;

将对表的多种访问结合考虑来确定分区的细度:

  • 大多数SQL操作的分区关键字值的范围;
  • 数据维护的需要,比如以月为单位删除历史数据;
  • 数据访问的性能,以操作范围确定的分区数据量还是过大,比如大于500万,则还需要进行细分;
  • 一个分区的数据量要小于500万,这是一个硬性的尺度,但从技术上来看,每个分区10万数据量的情况比每个分区20万数据量的情况要快很多,所以需要灵活掌握。
  1. 当各个分区中的数据能均等划分时性能最好,如果相差太大,则考虑采用其它分区,或者将大数据量的分区再进行 HASH 子分区;

  2. 各分区采用各自的表空间存储,使用 user_tab_partitions 字典来查看确定每个分区的表空间位置;

  3. 分区表的索引采用本地索引,因为常会根据分区关键字(比如时间)进行分区维护(比如删除1年前的数据,也就是删除1年前的分区),分区维护时全局索引会失效,而本地索引不会失效,这能保证访问表时索引正常可用。

4.3、LIST 分区的规范

大数据量的表需进行分区化,当表的数据量超过 500 万,需设计成分区表,当表的数据量超过 5000 万,请 DBA 参与设计。

SQL 常居于某列的散列值访问表,则对表使用 LIST 分区,LIST 分区不支持多列分区关键字;常见情况针对某个地区或者某个业务进行数据访问,那么就使用地区编号或者业务编号作为分区关键字。

将对表的多种访问结合考虑来确定分区的细度:

  • 一般使用一个分区关键字的值来划定一个分区;
  • 可以把分区关键字的值相对应数据比较少的几个分区合并作一个分区;- 如果一个分区关键字值所对应的数据量过大,比如大于 500 万,则应该对表采用 RANGE 分区,对该值的分区再采用 HASH 子分区;也就是说,一个可以采用 LIST 分区的表,肯定可以转化成 RANGE 分区(可带子分区),反之不然;
  • 一个分区的数据量要小于 500 万,这是一个硬性的尺度,但从技术上来看,每个分区 10 万数据量分区方法比每个分区 20 万数据量的分区方法要快很多,所以需要灵活掌握。
  1. 各分区采用各自的表空间存储,使用user_tab_partitions字典来确定每个分区的表空间;

  2. 分区表的索引采用本地索引。

4.4、HASH 分区的规范

大数据量的表需进行分区化,当表的数据量超过 500 万,需设计成分区表,当表的数据量超过 5000 万,请 DBA 参与设计。

SQL 访问表不按照某列的范围进行,也不按某列离散值进行,而且对该表的数据不会依据某列的值范围或者离散值进行定期维护,那么使用 HASH 分区;HASH 分区是不知道应该选择何种分区时的选择;HASH 分区的各分区都可能存有各种情况的数据,故而不能用于依据分区清理数据的情况。

对确定分区细度的考虑:

  • 依据分区的数据量规划和表的最大数据量来确定分区数;
  • 一个分区的数据量要小于 500 万,这是一个硬性的尺度,但从技术上来看,每个分区 10 万数据量分区方法比每个分区 20 万数据量的分区方法要快很多,所以需要灵活掌握。
  1. 各分区采用各自的表空间存储,使用 user_tab_partitions 字典来确定每个分区的表空间;

  2. 对于 HASH 分区表,大多数情况下依然要求采用本地索引,但是如果分区过细,也可以采用全局索引,因为根据 HASH 分区表的特征(各分区无业务区分,都有数据),该表很少会发生分区维护的工作。

4.5、RANGE-LIST 分区的规范

大数据量的表需进行分区化,当表的数据量超过 500 万,需设计成分区表,当表的数据量超过 5000 万,请 DBA 参与设计。

SQL 访问表时,既依据某列值的范围,又依据其他列的离散值或者范围,这种情况下采用 RANGE-LIST 复合分区,常用于语表中的数据需要依据一个时间字段做周期性删除等维护,并且正常业务 SQL 访问既依据时间字段,又依据其他字段的散列值进行访问的情况。

比如:电信增值业务计费表,既有时间又有业务属性列,统计的时候,会选择时间范围和业务属性,所以可以以时间列为分区关键字建立 RANGE 分区,以业务属性列为关键字建立 LIST 子分区;

分区划分的方法:

  • 就按照大多数范围访问的范围值来划定RANGE分区的范围,依据单个 LIST 子分区关键字的值来划分子分区;
  • 如果 LIST 子分区中数据量较小而且又常被一起访问的子分区可以合并成一个子分区;
  • 如果 LIST 子分区中一个子分区关键字值对应的子分区数据量还是很大,超过 500,影响性能,那么可以通过细分 RANGE 分区来达到减少 LIST 子分区数据量的目的,这点和 LIST 分区在该情况下的处理方法(转化成 RANGE-HASH)不同。
  1. 各子分区应该尽量分散到不同的表空间中存储,使用 user_tab_subpartitions 字典来确定每个子分区的表空间;

  2. RANGE-LIST 大多数情况采用本地索引,因为常根据 RANGE 分区关键字的来进行分区维护。

4.6、RANGE-HASH分区的规范

大数据量的表需进行分区化,当表的数据量超过 500 万,需设计成分区表,当表的数据量超过 5000 万,请 DBA 参与设计。

SQL 访问表时,主要依据某个列的范围进行访问,即访问特征符合 RANGE 分区的要求,或者数据维护特征符合 RANGE 分区的要求,但是以 SQL 或者维护的数据范围来划定分区,分区数据量又很大,对性能有影响,需再进行子分区,由于分区中的数据都会被访问到,所以子分区采用 HASH 方法,整个表就是 RANGE-HASH 分区;

划定分区的方法:先按照大多数范围访问的范围值来划定 RANGE 分区的范围,再依据性能情况来确定 HASH 子分区的数据量。

  1. 各子分区应该尽量分散到不同的表空间中存储,使用 user_tab_subpartitions 字典来确定每个子分区的表空间;

  2. RANGE-HASH 大多数情况采用本地索引,因为常根据 RANGE 分区关键字的来进行分区维护。

五、索引的设计规范

1、索引分类

Oracle 中可以创建多种类型的索引,以适应各种表的特点和各种查询条件的特点。可以按列的多少、索引列是否唯一、索引数据的组织形式对索引进行分类。

1.1、单列索引与复合索引

一个索引可以由一个或多个列组成,用来创建索引的列被称为“索引列”。

单列索引是基于单列所创建的索引,复合索引是基于两列或者多列所创建的索引。

1.2、唯一索引与非唯一索引

唯一索引 是索引列值不能重复的索引,非唯一索引 是索引列可以重复的索引。

无论是唯一索引还是非唯一索引,索引列都允许取 NULLc值。默认情况下,Oracle 创建的索引是不唯一索引。

1.3、B 树索引、位图索引与函数索引

B 树索引是按 B 树算法组织并存放索引数据的,所以 B 树索引主要依赖其组织并存放索引数据的算法来实现快速检索功能。

Oracle 中不仅能够直接对表中的列创建索引,还可以对包含列的函数或表达式创建索引,这种索引称为“函数索引”。

位图索引在多列查询时,可以对两个列上的位图进行 AND 和 OR 操作,达到更好的查询效果。

2、命名规范

命名规则: 类别码_表名_附加码,采用 大写字符

类别码: 一般索引 IDX、位图索引 BIDX、唯一索引 UK、主键 PK、外键 FK,类别码根据索引的性质填写。

表名: 表名应该简洁明了,尽量使用完整的单词,如果导致拼上表名后,长度超过30 个字符,则从最后一个单词开始,依次向前采用该单词的缩写。(如果没有约定的缩写,则采用该单词前4个字母来表示)。另外,表名中的名词单词都应使用单数形式,以免混淆,如:使用 FACTORY 而非 FACTORIES。

附加码: 可以是序号,也可以是字段名,根据实际的使用情况进行填写。

3、索引设计规范

索引是从数据库中获取数据的最高效方式之一,95% 的数据库性能问题都可以采用索引技术得到解决。

但大量的DML操作会增加系统对索引的维护成本,对性能会有一定影响,对于插入相当频繁的表要慎重建索引,索引也会占相当的存储空间,所以要 根据硬件环境和应用需求在空间和时间上达到最好的平衡点。

主要原则:

  • 适当利用索引提高查询速度:当数据量比较大,了解应用程序的会有哪些查询,依据这些查询需求建相应的索引;最好亲自试验一下,模拟一下生产环境的数据量,在此数据量下,比较一下建索引前后的查询速度;索引对性能会有一定影响,对于 DML 频繁列的索引要定期维护(重建)。但是,索引的结构对于索引的更新(比如在插入数据的时候)是有一定优化的,所以不要在没有试验以前过分夸大它对性能的影响,最终还是以试验为准。
  • 不要建实际用不上的索引,与上条相关,如果建的索引并不提高任何一应用中的查询速度,则要把它删除;有些数据库有相关工具可以发现实际未被使用的索引,可以利用一下。
  • 索引列的选择:如果检索条件有可能包含多列,创建联合主键或者联合索引,把最常用于检索条件的列放在最前端,其他的列排在后面;不要索引使用频繁的小型表,假如这些小表有频繁的 DML 就更不要建立索引,维护索引的代价远远高于扫描表的代价;
  • 主键索引在建立的时候一定要明确的指定名称,不能让系统默认建立主键索引(可能有些数据库无法指定主键名,则例外);
  • 当有联合主键或者联合索引时,注意不要建重复的索引。
    举例说明:
    表 EMPLOYEES,它的主键是建立在列 DEPARTID 和 EMPLOYEEID 上的联合主键,并且创建主键的语句中 DEPARTID 在前,EMPLOYEEID 在后。在这样一个表里,通常就没有必要再为 DEPARTID 建一个索引了,联合索引的情况也一样。
    更复杂的情况,比如表 EMPLOYEES,有一个索引建立在列 CORPID,DEPARTID, EMPLOYEEID 三列上,在创建语句中也依据上述顺序,就没有必要再为 CORPID 建立索引;也没有必要再建立以 CORPID 在前,DEPARTID 在后的联合索引;如果EMPLOYEEID 需要索引,那么为 EMPLOYEEID 建立一个索引是不与上面的索引重复的;DEPARTID 列也类似。
  • 控制一个表的索引数量,尽量使得一个表的索引数量小于五个。

3.1、指定表空间规范

每个索引在创建时,必须指定表空间,不要采用默认表空间,以防止索引建立在system 空间和非索引专用空间,以减少IO 冲突,提高性能。

3.2、主键索引的规范

对数据量表应该先在主键列建唯一索引,再建主键约束;分区表的主键必须采用该方法设计;原则上所有的数据表都要有主键。

主键上隐含索引,drop 或 disable 主键时,索引会丢失,为保证性能不变,为了对主键约束和相应索引有更多的控制,对大表(分区表)的索引采用如下方式建立:

  • 在准备建主键的列上建立唯一索引(UNIQUE INDEX):
CREATE UNIQUE INDEX Index_Name ON Table_Name(Column_Name) TABLESPACE  TBS_INDEX;
  • 再加上主键约束:
ALTER TABLE Table_Name ADD (PRIMARY KEY(Column_Name) USING INDEX TABLESPACE TBS_INDEX );

Oracle 会在指定的列上加上主键约束,并且使用该索引!

分区表的主键默认索引是全局索引,所以主键索引的分区方法:先建立分区化的唯一索引,再建主键约束。

3.3、唯一约束索引的规范

针对大数据量表应该先在唯一约束列上建立普通索引,再添加唯一性约束。分区表的唯一约束必须采用该方法。

删除或禁用唯一性约束通常同时使相关联的唯一索引失效,因而降低了数据库性能。

要避免这样问题,可以采取下面的步骤:

  • 在唯一性约束的列上创建非唯一性索引(普通索引);
  • 添加唯一性约束;

3.4、外键列索引的规范

对于关联两个表字段,一般应该分别建立主键、外键。实际是否建立外键,根据对数据完整性的要求决定。

为了提高性能,无论表的大小,外键都要建立索引,一是为了子父表关联查询的性能考虑,二是为了避免父子表修改而发生死锁。

对于有要求级联删除属性的外键,必须指定 on delete cascade

普通表的外键列建立普通索引即可,如果表是分区表,则依据表的情况建立本地索引或者全局索引。

3.5、复合索引的规范

复合索引只有在该种复合常被和该表相关的大多数 SQL 使用时才建立。复合索引的列数不能超过 5 个,否则该索引很少会被使用。

  • 复合索引的第一列,可以通过不使用该种复合的 SQL 来确定。假设一些 SQL 的 WHERE 中复合使用列为 ABC,而其他一些 SQL 的 WHERE 中常使用的是 C 列,那么该复合索引可以按照 CAB 的顺序建立,这样上述两种 SQL 都能使用该索引;
  • 对于不能把握好的复合索引,请在选择性大的列上分别建立单列索引;
  • 切忌不能将表相关的所有 SQL 中 WHERE 涉及到的列复合起来建立复合索引;

3.6、函数索引的规范

由于使用形式需和创建形式一致,尽量避免使用函数索引;如果想要使用函数索引,请尽量进行转化。

由于函数索引在使用时,使用形式必须和创建形式一致,故应该尽量避免使用函数索引,尽量采用如下方法转化 SQL 以避免函数索引的使用:

原本在 WHERE 中列上添加函数的,取函数的反意义函数添加到 = 另一侧的常数项上,这样只需要在列上建立普通索引即可,比如常见的日期转化函数:

TO_CHAR(CREATE_TIME)='2010-07-07
-- 采用TO_DATE() 转化为
CREATE_TIME=TO_DATE('2010-07-07','yyyy-mm-dd')

3.7、位图索引的规范

静态表中的低基数列可以使用位图索引。在事务型数据库(OLTP)中禁止使用位图(bitmap)索引,在报表型数据库(OLAP)中的静态表,可以适当使用。

3.8、反向索引的规范

列值顺序增加的列,其上的WHERE运算是<>或者=而不是范围(between and或者 < and >)检索时,可以采用反向函数。一般创建反向索引的列为 NUMBER 类型,值由 SEQUENCE 生成。

3.9、分区索引的规范

对分区表的索引,需要做分区维护的,必须使用局部索引。一般情况下,HASH 分区表可以采用全局索引,其他分区,包括 RANGE-HASH 也应该采用本地索引,主要是由于 HASH 分区表不常进行分区维护。

3.10、索引重建的规范

重建索引使用 ALTER INDEX REBUILD 方式,禁止采用 DROP INDEX & CREATE INDEX 方式。

分区表等大数据量表的索引必须采用 ALTER INDEX REBUILD 方式重建。

方法:

ALTER INDEX IDX_NAME REBUILD [TABLESPACE TBSP_NAME]

六、其他数据库对象设计规范

1、命名规范