数据库表设计、 数据库分层、myslq水平拆分、oracle表分区

数据库表设计

数据库表结构设计方法及原则(li)
  数据库设计的三大范式:为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

  在实际开发中最为常见的设计范式有三个:第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式;第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中;第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。总结一下,就是:第一范式(确保每列保持原子性);第二范式(确保表中的每列都和主键相关);第三范式(确保每列都和主键列直接相关,而不是间接相关)。

  在目前的企业信息系统中,数据库还是最佳的数据存储方式,虽然已经有很多的书籍在指导我们进行数据库设计,但应该那种方式是设计数据库的表结构的最好方法、设计时应遵从什么样的原则、四个范式如何能够用一种方式达到顺畅的应用等是我一直在思考和总结的问题,下文是我针对这几个问题根据自己的设计经历准备总结的一篇文章的提纲,欢迎大家一块进行探讨,集思广益。其中提到了领域建模的概念,但未作详细解释,希望以后能够有时间我们针对这个命题进行深入探讨。

  1.不应该针对整个系统进行数据库设计,而应该根据系统架构中的组件划分,针对每个组件所处理的业务进行组件单元的数据库设计;不同组件间所对应的数据库表之间的关联应尽可能减少,如果不同组件间的表需要外键关联也尽量不要创建外键关联,而只是记录关联表的一个主键,确保组件对应的表之间的独立性,为系统或表结构的重构提供可能性。

//注意他这里说的是"不要创建外键关联",创建外键关联的语句是:
//foreign key(member_id) references member (id);
//我们几乎没有用到这条语句,因为我们就是这样做的,用到外键时,只是记录关联表的主键,而非在数据库级别上创建外键。
//也不知道是歪打正着,还是前辈DBA过于强大,已经考虑好了。
  2.采用领域模型驱动的方式和自顶向下的思路进行数据库设计,首先分析系统业务,根据职责定义对象。对象要符合封装的特性,确保与职责相关的数据项被定义在一个对象之内,这些数据项能够完整描述该职责,不会出现职责描述缺失。并且一个对象有且只有一项职责,如果一个对象要负责两个或两个以上的职责,应进行分拆。

// 领域模型驱动的方式,目前用的还不是很熟,考虑的不够多。因为经常的数据库中的表只是拿来做存储用而已,
//特别是小需求,要加什么字段,找到相关表加上去就行了,不太考虑领域模型。这个在中文站老业务表里很常见
  3.根据建立的领域模型进行数据库表的映射,此时应参考数据库设计第二范式:一个表中的所有非关键字属性都依赖于整个关键字。关键字可以是一个属性,也可以是多个属性的集合,不论那种方式,都应确保关键字能够保证唯一性。在确定关键字时,应保证关键字不会参与业务且不会出现更新异常,这时,最优解决方案为采用一个自增数值型属性或一个随机字符串作为表的关键字。

  4.由于第一点所述的领域模型驱动的方式设计数据库表结构,领域模型中的每一个对象只有一项职责,所以对象中的数据项不存在传递依赖,所以,这种思路的数据库表结构设计从一开始即满足第三范式:一个表应满足第二范式,且属性间不存在传递依赖。

//数据库三范式记不得的同学去查资料温习一下。
//个人认为第三范式的目的是尽量减少数据冗余,保证相同的数据只存在一份。
//第三范式其实我们遵守的并不是很严格,特别是老的数据库表中会有冗余字段。这个要看情况决定吧。
  5.同样,由于对象职责的单一性以及对象之间的关系反映的是业务逻辑之间的关系,所以在领域模型中的对象存在主对象和从对象之分,从对象是从1-N或N-N的角度进一步完善主对象的业务逻辑,所以从对象及对象关系映射为的表及表关联关系不存在删除和插入异常。

//最后一句看不懂,可能是"所以表及表关联关系不应该出现删除和插入异常。"?
  6.在映射后得出的数据库表结构中,应再根据第四范式进行进一步修改,确保不存在多值依赖。这时,应根据反向工程的思路反馈给领域模型。如果表结构中存在多值依赖,则证明领域模型中的对象具有至少两个以上的职责,应根据第一条进行设计修正。第四范式:一个表如果满足BCNF,不应存在多值依赖。 

复制代码
//第四范式我们遵守的并不多吧。
//例如:
//VAS_WP_CONFIG.config_name字段的值包括:adv(广告主题)/glare(炫彩滚动主题)/theme_simple(普通主题)/theme_cartoon(动画主题)/ theme_none(不显示背景主题)
//cate_background(类目背景)/video(公司视频)/board_cartoon(动画招牌)/board_simple(普通招牌)等。
//如果遵守第四范式,则需要新增一张VAS_WP_CONFIG_NAME表,存储配置名称枚举值,而VAS_WP_CONFIG.config_name字段改为VAS_WP_CONFIG.config_name_id。
//这样做更利于扩展,不会因为每个人的理解不一致而向VAS_WP_CONFIG.config_name字段里设置乱七八糟的值,但是这样需要维护更多的小表,造成数据值表的数量膨胀,DBA可能会觉得管理上有更多的困难。
//我们采用潜规则约定、java枚举类等其它方式来进行保证。但有时候效果并不是很好,经常发现旧数据库表中枚举字段的值五花八门,不全是约定的。
复制代码
  7.在经过分析后确认所有的表都满足二、三、四范式的情况下,表和表之间的关联尽量采用弱关联以便于对表字段和表结构的调整和重构。并且,我认为数据库中的表是用来持久化一个对象实例在特定时间及特定条件下的状态的,只是一个存储介质,所以,表和表之间也不应用强关联来表述业务(数据间的一致性),这一职责应由系统的逻辑层来保证,这种方式也确保了系统对于不正确数据(脏数据)的兼容性。当然,从整个系统的角度来说我们还是要尽最大努力确保系统不会产生脏数据,单从另一个角度来说,脏数据的产生在一定程度上也是不可避免的,我们也要保证系统对这种情况的容错性。这是一个折中的方案。

  8.应针对所有表的主键和外键建立索引,有针对性的(针对一些大数据量和常用检索方式)建立组合属性的索引,提高检索效率。虽然建立索引会消耗部分系统资源,但比较起在检索时搜索整张表中的数据尤其时表中的数据量较大时所带来的性能影响,以及无索引时的排序操作所带来的性能影响,这种方式仍然是值得提倡的。

//索引目前都是DBA根据具体的SQL来创建的,不过开发写SQL时,也应该适当考虑一下字段的索引。
  9.尽量少采用存储过程,目前已经有很多技术可以替代存储过程的功能如"对象/关系映射"等,将数据一致性的保证放在数据库中,无论对于版本控制、开发和部署、以及数据库的迁移都会带来很大的影响。但不可否认,存储过程具有性能上的优势,所以,当系统可使用的硬件不会得到提升而性能又是非常重要的质量属性时,可经过平衡考虑选用存储过程。

//目前都是杜绝使用存储过程的,我觉得用起来比较方便,对于我们来说,主要原因是会给DBA带来管理方面的麻烦,
//因为时间一长,存储过程的逻辑和使用场景,往往没人能了解,容易产生更多问题
  10.当处理表间的关联约束所付出的代价(常常是使用性上的代价)超过了保证不会出现修改、删除、更改异常所付出的代价,并且数据冗余也不是主要的问题时,表设计可以不符合四个范式。四个范式确保了不会出现异常,但也可能由此导致过于纯洁的设计,使得表结构难于使用,所以在设计时需要进行综合判断,但首先确保符合四个范式,然后再进行精化修正是刚刚进入数据库设计领域时可以采用的最好办法。

  11.设计出的表要具有较好的使用性,主要体现在查询时是否需要关联多张表且还需使用复杂的SQL技巧。我感觉遵守的范式越多,就越使SQL复杂,具体情况具体分析。设计出的表要尽可能减少数据冗余,确保数据的准确性,有效的控制冗余有助于提高数据库的性能

  因此,考虑了以上条件之后,表设计约定规则如下:

复制代码
//规则1:表必须要有主键。
//规则2:一个字段只表示一个含义。
//规则3:总是包含两个日期字段:gmt_create(创建日期),gmt_modified(修改日期),且这两个字段不应该包含有额外的业务逻辑。
//规则4:MySQL中,gmt_create、gmt_modified使用DATETIME类型。
//规则5:禁止使用复杂数据类型(数组,自定义类型等)。
//规则6: MySQL中,附属表拆分后,附属表id与主表id保持一致。不允许在附属表新增主键字段。
//规则7: MySQL中,存在过期概念的表,在其设计之初就必须有过期机制,且有明确的过期时间。过期数据必须迁移至历史表中。
//规则8: MySQL中,不再使用的表,必须通知DBA予以更名归档。
//规则9: MySQL中,线上表中若有不再使用的字段,为保证数据完整,禁止删除。
//规则10: MySQL中,禁止使用OCI驱动,全部使用THI驱动。
复制代码
关于MySQL的部分学习笔记总结:

一、事务跟存储引擎

  1.四种事务隔离级别:read uncommited, read commited(大多数db默认的),repeatable read(mysql默认), seriazable。

  2.mysql是默认的auto commited, 也就是说每次查询默认都是自动提交的(show variables like 'autocommited')。mysql可以通过set transaction isolatioin level命令来设置隔离级别,例如:set session transaction isolation level read commited。

  3.mysql中像innodb采用mvcc(多版本并发控制)来处理并发。mvcc只工作在read commited,repeatable read这两种事务隔离级别上。read uncommited隔离级别不兼容mvcc是因为在该级别得下的查询,不读取符合当前事务版本的数据行,而是最新版本的数据行。seriazable隔离级别不兼容MVCC,因为该级别下的读操作会对每个返回行进行加锁。

  4.选择存储引擎,并发选用myisam,事务选择innodb,myisam比innodb更容易出错,出错了恢复的时间也比较长。只有myisam支持全文检索。

  5.把表从一种存储引擎转到另一种引擎:

//  1.    alter table mytable engine=falcon;  操作费时,可能会占用服务器的所有i/o处理能力。
//  2.    create table innodb_table like myisam_table;
//        alter table innodb_table engine=innodb;
//        insert into innodb_table select * from myisam_table;
二、数据类型

  1.尽可能的要把field定义为Not NULL, mysql比较难优化使用了可空列的查询,它会使索引,索引统计更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理,当可空列被索引时,每条记录都需要一个格外的字节。 即使要在表中存储"没有值"的字段,考虑使用0,特殊字段或者空字符串来代替。

  2.datetime与timestamp能保存同样的数据:精确度为秒,但是timestamp使用的空间只有datetime的一半,还能保存时区,拥有特殊的自动更新能力。但是timestamp保存的时间范围要比datetime要小得多。mysql能存储的最细的时间粒度为秒

  3.mysql支持很多种别名,如bool,integer,nummeric.

  4.float与double类型支持使用标准的浮点运算进行近似计算。 Decimal类型保存精确的小数,在>=mysql5.0,mysql服务器自身进行了decimal的运算,因为CPU不支持直接对它进行运算,所以慢一点。

  5.mysql会把text与blob类型的列当成有实体的对象来进行保存。他们有各自的数据类型家族(tinytext,smalltext,text,mediumtext,longtext; blob类似); mysql对blob与text列排序方式和其他类型有所不同,它不会按照字符串的完整长度来排序。而只是按照max_sort_length规定的若干个字节来进行排序。

  6.采用enum来代替字符串类型。mysql在内部把每个枚举值都保存为整数。enum在内部是按照数字进行排序的,而不是按照字符串。enum最不好的就是字符串列表是固定的,添加和删除必须使用alter table。

  7.ip地址,一般会采用varchar(15)列来保存。事实上,IP地址是个无符号的32位整数,而不是字符串。mysql提供了inet_aton()和inet_nota()函数在证书与ip地址之间进行转换。

三、索引

  1.聚集索引不仅仅是一种单独的索引类型,而且是一种存储数据的方式。Innodb引擎的聚集索引实际上在同样的结构中保存了B-Tree索引和数据行。当表有聚集索引时,它的数据行实际上保存在索引的叶子上。注意是存储引擎来实现索引。

  2.myisam与innodb数据布局:myisam索引树(无论是主键索引还是非主键索引)叶子节点都是指向的数据行,而innodb中聚集索引,主键索引树叶子节点就带得有数据的内容,而非主键索引树中叶子节点指向主键值,而不是数据的位置。

  3.mysql有两种产生排序结果的方式:使用文件排序,或者扫描有序的索引。目前只有myisam支持全文索引。

  4.myisam表有表级锁;myisam表不支持事务,实际上,myisam并不保证单条命令完成;myisam只缓存了mysql进程内部的索引,并保存在键缓存区内。OS缓存了表的数据;行被紧密的保存在一起,磁盘上的数据有很小的磁盘占用和快速的全表扫描。

  5.innodb支持事务和四种事务隔离级别;在mysql5.0中,只有innodb支持外鍵;支持行级锁与mvcc;所有的innodb表都是按照主键聚集的;所有索引(出开主键)都是按主键引用行;索引没有使用前缀压缩,因此索引可能比myisam大很多;数据转载缓慢;阻塞auto_increment,也就是用表级锁来产生每个auto_increment。

四、MYSQL性能分析

  1.mysql提供了一个benchmark(int 循环次数,char* 表达式); 可以分析表达式执行所花时间。 例如:

// select BENCHMARK(10000,SHA1('aaaaaaaaaaaaaaaa'))
  2.mysql有两种查询日志:普通日志和慢速日志。

五、MYSQL高级特性

  1.在mysql中,只有myisam存储引擎支持全文索引。myisam全文索引是一种特殊的具有两层结构的B树。

  2.存储引擎事务在存储引擎内部被赋予acid属性,分布式(XA)是一种高层次事务,它可以历哟内部个两段提交的方式将acid属性扩展到存储引擎外部,甚至数据库外部。阶段1:通知所有提交者准备提交 阶段2:通知所有参与者进行真正提交。

  3.mysql 的字符集和校对规则有 4 个级别的默认设置:服务器级、数据库级、表级和字段级。Mysql4.1 开始支持 SQL 的子查询。

复制代码
/******************************************/
/*   数据库全名 = [email protected]:3318【mysql】   */
/*    表名称 = task_new   */
/******************************************/
CREATE TABLE `task_new` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `task_name` varchar(128) NOT NULL COMMENT '任务名称',
  `image` varchar(128) DEFAULT NULL COMMENT '任务图标',
  `description` varchar(1024) NOT NULL COMMENT '任务描述',
  `content` varchar(1024) NOT NULL COMMENT '任务内容',
  `finished_message` varchar(128) DEFAULT NULL COMMENT '任务完成提示信息',
  `task_scope` int(11) NOT NULL COMMENT '任务范围, 0-平台任务, 1-游戏任务',
  `series_task` int(11) NOT NULL DEFAULT '0' COMMENT '任务类型: 系列任务,单独任务',
  `task_type` int(11) NOT NULL DEFAULT '0' COMMENT '任务类型: 固定任务, 推广任务, 日常任务',
  `pre_task` varchar(128) DEFAULT NULL COMMENT '前置任务',
  `post_task` varchar(128) DEFAULT NULL COMMENT '后置任务',
  `task_status` int(11) NOT NULL COMMENT '任务状态, 待审核、未开始、生效中、已暂停、已完成、审核未通过',
  `auto_task` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否手动任务, 0-否, 1-是',
  `is_required` tinyint(4) NOT NULL COMMENT '是否必须任务',
  `event_type` varchar(64) DEFAULT NULL COMMENT '关心的事件类型',
  `task_target` bigint(20) DEFAULT '0' COMMENT '任务目标',
  `reset_num` int(11) NOT NULL COMMENT '重置次数',
  `reset_cycle` int(11) NOT NULL COMMENT '重置周期',
  `task_interval` int(11) NOT NULL COMMENT '任务间隔',
  `xiaoer` bigint(20) unsigned NOT NULL COMMENT '创建人',
  `review_id` bigint(20) unsigned NOT NULL COMMENT '审核人ID',
  `last_start_time` datetime DEFAULT NULL COMMENT '上次生效时间',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '修改时间',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `start_condition` varchar(1024) NOT NULL COMMENT '任务触发条件',
  `end_condition` varchar(1024) NOT NULL COMMENT '任务完成条件',
  `enable` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否可用',
  `rule` varchar(4096) NOT NULL COMMENT '任务规则',
  `priority` int(11) NOT NULL DEFAULT '1' COMMENT '任务优先级',
  `progress_rule` varchar(2048) NOT NULL DEFAULT '' COMMENT '进度计算规则',
  `order_no` int(11) DEFAULT '1' COMMENT '排序号',
  `classification` int(11) DEFAULT '0' COMMENT '0:默认分类\n1:玩游戏\n2:抽奖',
  `level` int(11) DEFAULT '0' COMMENT '针对同一个分类,不同的等级',
  `ext1` longtext COMMENT '扩展字段1(UU中使用该字段指示按钮跳转)',
  `ext2` longtext COMMENT '扩展字段2,暂时预留',
  `channel` int(11) DEFAULT '0' COMMENT '任务渠道:0-uu或者1-game_box',
  `consecutive_day` int(11) DEFAULT '1' COMMENT '连续完成任务的天数',
  `activity` varchar(256) DEFAULT 'default' COMMENT '任务所属的活动名字',
  `device` text COMMENT '机型',
  `packages` text COMMENT '应用',
  PRIMARY KEY (`id`),
  KEY `name_channel` (`task_name`,`channel`),
  KEY `activity` (`activity`(255))
) ENGINE=InnoDB AUTO_INCREMENT=1194 DEFAULT CHARSET=utf8 COMMENT='任务表';
---------------------------------------------------------------------------------------
数据库表设计总结
一、实体与表对应关系

表<=>实体,字段<=>属性。

 

二、表与表的关系(实体间的关系):一对一、一对多、多对多

一对一:一条记录只对应其他表中的一条记录有关系

学生基本信息表t_student,成绩表t_studentScore含有一个外键studentId。基本信息表中的studentId和成绩表中的studentId就是一对一的关系。

一对多:A表一条记录对应B表中多条记录有关系,B表的记录不被A表记录共享(有关系)。

班级表和学生表,一个班级有多个学生,对班级来说就是一对多的关系。



多对多:A表一条记录和B表多条记录有关系,B表的一条记录也和A表的多条记录有关系(互相共享)。

学生表和科目表,学生可以选择多个科目,每个科目可以被多个学生选择。



三、基本表的完整性

(1)原子性。字段是不可再分解的。
(2)原始性。记录是原始数据(基础数据)的记录。
(3)稳定性。结构是相对稳定的,表中的记录是要长期保存的。
(4)演绎性。由基本表与代码表中的数据,可以派生出所有的输出数据。

 

四、其他常用表

1.中间表

中间表是针对多对多关系的。就比如做公交查询系统,里面有两个表,分别是车站表t_busstation、线路表t_road,根据常识,一个站有多个线路经过,而每个线路又有多个车站,怎么才能将两个表联系起来呢,如果是一对一,一对多,我们一个表, 两个表就可以将他们实现了。但是多对多呢,这样我们就必须借助中间表用来连接两个表。一般中间表只有一个自增主键+两个表的主键。中间表是没有属性的因为它不是一个基本表。

 

2.临时表

临时表是那些以#号开头为名字的数据表,它主要是用来存放临时数据的,当用户断开连接但没有清除临时表里的数据时,系统会自动把临时表里的数据清空。临时表是放在系统数据库 tempdb中的,而不是当前数据库。

 

临时表分两种:本地临时表和全局临时表。

a.本地临时表

本地临时表是以#开头的,只对当前的数据库用户可见,而其他的用户是不可见的。当数据库实例断开后当然也就丢失了数据了,不管是显式清空还是系统回收。 

b.全局临时表

以“##”开头的,而且是对所有的用户都是可见,当你断开数据库实例连接时,只要还有别的系统项目在引用它,连着数据库,那么数据就存在,只有当别的系统也全部断开连接时,系统才会清除全局临时表的数据。

 

建立临时表的语句:

复制代码
 本地临时表:
 create table #student
(
  studentID int ,
  studentName nvarchar (40),
  classID int
 )
复制代码
复制代码
 全局临时表:
 create table ##student
(
  studentID int ,
  studentName nvarchar (40).
  classID int
 )      
复制代码
也可以用SQL语句完成:

select * from employee into  #student
 

五、三大范式


第一范式:如果每列(或者每个属性)都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式.比如一个工人的基本信息表,里面有工人的工号,性别,年龄,这些属性都是不可分割的,所以这个表就符合了第一范式。

第二范式: 就是在第一范式的基础上延伸,使之表里的每个字段都与主键发生关系。假如一个关系满足第一范式,并且除了主键以外的其它字段,都依赖于该主键,则满足第二范式.
例如:订单表(订单编号、产品编号、定购日期、价格、……),"订单编号"为主键,"产品编号"和主键列没有直接的关系,即"产品编号"列不依赖于主键列,这个列我们就可以把它删除。

第三范式:在第二范式的基础上更进一步,也就是为了实现表里的列都与主键列直接相关,不是间接相关。这个我们可以用“Armstrong 公理”中的传递规则来推理。

定义:
设U是关系模式R 的属性集,F 是R 上成立的只涉及U 中属性的函数依赖集。若X→Y 和 Y→Z在R 上成立,则X →Z 在R 上成立。因此我们就来看在网上搜索到的例子:例如:订单表(订单编号,定购日期,顾客编号,顾客姓名,……),初看该表没有问题,满足第二范式,每列都和主键列"订单编号"相关,再细看你会发现"顾客姓名"和"顾客编号"相关,"顾客编号"和"订单编号"又相关,最后经过传递依赖,"顾客姓名"也和"订单编号"相关。为了满足第三范式,应去掉"顾客姓名"列,放入客户表中。

这里其实就是为了说明数据库的表里步要出现冗余,在顾客表里已经有了"顾客姓名"了,而在订单表里就别出现了,而直接根据顾客编号相关联就可以,否则造成资源浪费。


三大范式延伸:

第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;
第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。

其实在设计数据库的时候我们最多的要遵循的就是第三范式,但是并不是越满足第三范式数据库就设计的越完美,这种错误是错误的。有时候增加点冗余相反的会提高访问速率,因此在实际的设计过程中应降低对范式的要求。

以前对数据冗余并不是很了解,在百度知道里的定义是这样的:在一个数据集合中重复的数据称为数据冗余. 但是不是说我们表的主键在其他表里重复出现就是冗余,这不是,而是为了连接两个表。只有非键字段就是既不是主键外键等约束的键如果重复出现,就会形成数据冗余。数据冗余也包括重复性冗余和派生冗余。比如工人表里有"基本工资","奖金"两列,然后还有一个"总工资"的列,这个总工资就是派生冗余。低级的重复性冗余一定要避免,杜绝,但是像派生冗余还是提倡的因为它能提高访问的效率。

 

个人总结:

事物的属性对应表的属性,将一张表看作一个事物。如,书的属性有价格、重量、等等。(一般表都有Id来区分每条记录)
----------------------------------------------------------------------------

数据库分层

数据仓库分层的原因

1通过数据预处理提高效率,因为预处理,所以会存在冗余数据

2如果不分层而业务系统的业务规则发生变化,就会影响整个数据清洗过程,工作量巨大

3通过分层管理来实现分步完成工作,这样每一层的处理逻辑就简单了



标准的数据仓库分层:ods(临时存储层),pdw(数据仓库层),mid(数据集市层),app(应用层)

ods:历史存储层,它和源系统数据是同构的,而且这一层数据粒度是最细的,这层的表分为两种,一种是存储当前需要加载的数据,一种是用于存储处理完后的数据。

pdw:数据仓库层,它的数据是干净的数据,是一致的准确的,也就是清洗后的数据,它的数据一般都遵循数据库第三范式,数据粒度和ods的粒度相同,它会保存bi系统中所有历史数据

mid:数据集市层,它是面向主题组织数据的,通常是星状和雪花状数据,从数据粒度将,它是轻度汇总级别的数据,已经不存在明细的数据了,从广度来说,它包含了所有业务数量。从分析角度讲,大概就是近几年

app:应用层,数据粒度高度汇总,倒不一定涵盖所有业务数据,只是mid层数据的一个子集。



数据仓库的目的是构建面向分析的集成化数据环境,为企业提供决策支持。数据仓库的context也可以理解为:数据源,数据仓库,数据应用



数据仓库可以理解为中间集成化数据管理的一个平台

etl(抽取extra,转化transfer,装载load)是数据仓库的流水线,也可以认为是数据仓库的血液。

数据仓库的存储并不需要存储所有原始数据,因为比如你存储冗长的文本数据完全没必要,但需要存储细节数据,因为需求是多变的,而且数据仓库是导入数据必须经过整理和转换使它面向主题,因为前台数据库的数据是基于oltp操作组织优化的,这些可能不适合做分析,面向主题的组织形式才有利于分析。

多维数据模型就是说可以多维度交叉查询和细分,应用一般都是基于联机分析处理(online analytical process OLAP),面向特定需求群体的数据集市会基于多位数据模型构建

而报表展示就是将聚合数据和多维分析数据展示到报表,提供简单和直观的数据。

元数据,也叫解释性数据,或者数据字典,会记录数据仓库中模型的定义,各层级之间的映射关系,监控数据仓库的数据状态和etl的任务运行状态。一般通过元数据资料库来统一存储和管理元数据。


------------------------------------------------------------------------------
-基本查询
select id,c_mmcode,c_mmroomname,c_parentId from s_mmroom;
--层次化查询
select id,c_mmcode,c_mmroomname,c_parentId from s_mmroom 
start with c_parentId='0' connect by prior id = c_parentId;
--使用level节点
select level,id,c_mmcode,c_mmroomname,c_parentId from s_mmroom
start with c_parentId='0' connect by prior id = c_parentId;
--查询层次数
select count(distinct level) from s_mmroom 
start with c_parentId='0' connect by prior id = c_parentId;
---查询结果的层次化
select level,LPAD(' ',2*LEVEL-1)||' '||c_mmcode||' '||c_mmroomname,c_parentId from s_mmroom
start with c_parentId='0' connect by prior id = c_parentId;
--从非根节点开始遍历 
select level,LPAD(' ',2*LEVEL-1)||' '||c_mmcode||' '||c_mmroomname,c_parentId from s_mmroom
start with c_parentId='00000000000000000000000000000000' connect by prior id = c_parentId;
--在START WITH中使用子查询  下面这个查询使用子查询来选择指定节点的。然后传给START WITH子句。
select level,lpad(' ',2*level-1)||' '||c_mmroomname from s_mmroom
start with c_parentId=(select id from s_mmroom where c_mmroomname like '%中国船级社%')
connect by prior id =c_parentId;
--从下向上遍历树  不一定非要按照从父节点到子节点的顺序从上至下遍历树;也可以从某个子节点开始,从下而上遍历。实现的方法是交换父节点和子节点在CONNECT BY PRIOR子句中的顺序
select level,lpad(' ',2*level-1)||' '||c_mmroomname from s_mmroom
start with c_parentId='00000000000000000000000000000000' connect by prior id = c_parentId;
--从层次化查询中删除节点和分支   略 表名后面加where
--在层次化查询中加入其它条件      略 表名后面加where
------------------------------------------------------------------
BLOCK(块)

block 是oracle中最小的分配单位,也是最小的I/O单位,可以在创建数据库时设定block的大小,可以设为任意的大小,但为了支持与兼容方面,建议现实中设为2的幂,例如:

2KB、4KB、8KB,最大为32KB。



EXTENT(区)

extent 是由逻辑连续的block组成的,注意是逻辑上的,一般来讲,文件本身在磁盘上不是连续的,extent的大小可能是一个block的大小,也可能大到2GB,



SEGMENT(段)

segment 是由一个或多个extent组成的,segment就是占用存储空间的数据库对象,例如表、索引等,segment在tablespace中,但可以包含这个tablespace中的多个数据文件中的数据



TABLESPACE(表空间)

ORACLE数据库是由一个或多个TABLESPACE构成,什么是TABLESPACE? 一种逻辑上的存储容器,包括一个或多个datafile(数据文件)。tablespace可以包含多个segment(表段,索引段等),但一个segment只能属于一个tablespace。

                                              

上图中圆柱形代表datafile(user_data01.db和user_data02.dbf),T1,T2,I1是3个segment(可能是2个表和1个索引),这个USER_DATA  TABLESPACE分配了4个extent,T1和I1各有一个extent。如果这个TABLESPACE需要更多的空间,可以调整已分配给该TABLESPACE的datafile的大小,也可以增加第三个datafile。

要说明的是上述所讨论的概念除了block都是逻辑上的概念,所谓连续的也是逻辑上连续的。
-------------------------------------------------------------------------------

myslq水平拆分

mysql的水平拆分和垂直拆分
1,水平分割:

例:QQ的登录表。假设QQ的用户有100亿,如果只有一张表,每个用户登录的时候数据库都要从这100亿中查找,会很慢很慢。如果将这一张表分成100份,每张表有1亿条,就小了很多,比如qq0,qq1,qq1...qq99表。

用户登录的时候,可以将用户的id%100,那么会得到0-99的数,查询表的时候,将表名qq跟取模的数连接起来,就构建了表名。比如123456789用户,取模的89,那么就到qq89表查询,查询的时间将会大大缩短。

这就是水平分割。

 

2,垂直分割:

垂直分割指的是:表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。

例如学生答题表tt:有如下字段:

Id name 分数 题目 回答

其中题目和回答是比较大的字段,id name 分数比较小。

如果我们只想查询id为8的学生的分数:select 分数 from tt where id = 8;虽然知识查询分数,但是题目和回答这两个大字段也是要被扫描的,很消耗性能。但是我们只关心分数,并不想查询题目和回答。这就可以使用垂直分割。我们可以把题目单独放到一张表中,通过id与tt表建立一对一的关系,同样将回答单独放到一张表中。这样我们插叙tt中的分数的时候就不会扫描题目和回答了。

 

3,其他要点:

1)存放图片、文件等大文件用文件系统存储。数据库只存储路径,图片和文件存放在文件系统,甚至单独存放在一台服务器(图床)。

2)数据参数配置。

最重要的参数就是内存,我们主要用的innodb引擎,所以下面两个参数调的很大:

innodb_additional_mem_pool_size=64M

innodb_buffer_pool_size=1G

对于MyISAM,需要调整key_buffer_size,当然调整参数还是要看状态,用show status语句可以看到当前状态,以决定该调整哪些参数。

 

4,合理的硬件资源和操作系统

如果机器的内存超过4G,那么应当采用64位操作系统和64位MySQL。

 

 

案例:

    简单购物系统暂设涉及如下表:

1.产品表(数据量10w,稳定)

2.订单表(数据量200w,且有增长趋势)

3.用户表 (数据量100w,且有增长趋势)

以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万

 

垂直拆分:

解决问题:

表与表之间的io竞争

不解决问题:

单表中数据量增长出现的压力

方案:

把产品表和用户表放到一个server上

订单表单独放到一个server上

 

水平拆分:

解决问题:

单表中数据量增长出现的压力

不解决问题:

表与表之间的io争夺

 

方案:

用户表通过性别拆分为男用户表和女用户表

订单表通过已完成和完成中拆分为已完成订单和未完成订单

产品表 未完成订单放一个server上

已完成订单表盒男用户表放一个server上

女用户表放一个server上
-----------------------------------------------------------------------------
目前很多互联网系统都存在单表数据量过大的问题,这就降低了查询速度,影响了客户体验。为了提高查询速度,我们可以优化sql语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度还是满足不了要求。这时候我们就可以通过分表降低单次查询数据量,从而提高查询速度,一般分表的方式有两种:水平拆分和垂直拆分,两者各有利弊,适用于不同的情况。

水平拆分 
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。 
这里写图片描述 
这里写图片描述 
通常情况下,我们使用取模的方式来进行表的拆分;比如一张有400W的用户表users,为提高其查询效率我们把其分成4张表users1,users2,users3,users4 
通过用ID取模的方法把数据分散到四张表内Id%4+1 = [1,2,3,4] 
然后查询,更新,删除也是通过取模的方法来查询。

例:QQ的登录表。假设QQ的用户有100亿,如果只有一张表,每个用户登录的时候数据库都要从这100亿中查找,会很慢很慢。如果将这一张表分成100份,每张表有1亿条,就小了很多,比如qq0,qq1,qq1…qq99表。

用户登录的时候,可以将用户的id%100,那么会得到0-99的数,查询表的时候,将表名qq跟取模的数连接起来,就构建了表名。比如123456789用户,取模的89,那么就到qq89表查询,查询的时间将会大大缩短。

另外部分业务逻辑也可以通过地区,年份等字段来进行归档拆分;进行拆分后的表,只能满足部分查询的高效查询需求,这时我们就要在产品策划上,从界面上约束用户查询行为。比如我们是按年来进行归档拆分的,这个时候在页面设计上就约束用户必须要先选择年,然后才能进行查询;在做分析或者统计时,由于是自己人的需求,多点等待其实是没关系的,并且并发很低,这个时候可以用union把所有表都组合成一张视图来进行查询,然后再进行查询。

水平拆分的优点: 
◆表关联基本能够在数据库端全部完成; 
◆不会存在某些超大型数据量和高负载的表遇到瓶颈的问题; 
◆应用程序端整体架构改动相对较少; 
◆事务处理相对简单; 
◆只要切分规则能够定义好,基本上较难遇到扩展性限制;

水平切分的缺点: 
◆切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则; 
◆后期数据的维护难度有所增加,人为手工定位数据更困难; 
◆应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。

垂直拆分 
垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。 
这里写图片描述 
这里写图片描述

通常我们按以下原则进行垂直拆分: 
1,把不常用的字段单独放在一张表;, 
2,把text,blob等大字段拆分出来放在附表中; 
3,经常组合查询的列放在一张表中;

例如学生答题表tt:有如下字段: 
Id name 分数 题目 回答 
其中题目和回答是比较大的字段,id name 分数比较小。

如果我们只想查询id为8的学生的分数:select 分数 from tt where id = 8;虽然知识查询分数,但是题目和回答这两个大字段也是要被扫描的,很消耗性能。但是我们只关心分数,并不想查询题目和回答。这就可以使用垂直分割。我们可以把题目单独放到一张表中,通过id与tt表建立一对一的关系,同样将回答单独放到一张表中。这样我们插叙tt中的分数的时候就不会扫描题目和回答了。

垂直切分的优点 
◆ 数据库的拆分简单明了,拆分规则明确; 
◆ 应用程序模块清晰明确,整合容易; 
◆ 数据维护方便易行,容易定位;

垂直切分的缺点 
◆ 部分表关联无法在数据库级别完成,需要在程序中完成; 
◆ 对于访问极其频繁且数据量超大的表仍然存在性能平静,不一定能满足要求; 
◆ 事务处理相对更为复杂; 
◆ 切分达到一定程度之后,扩展性会遇到限制; 
◆ 过读切分可能会带来系统过渡复杂而难以维护。
-----------------------------------------------------------------------
数据库垂直拆分 水平拆分
        当我们使用读写分离、缓存后,数据库的压力还是很大的时候,这就需要使用到数据库拆分了。
        
        数据库拆分简单来说,就是指通过某种特定的条件,按照某个维度,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面以达到分散单库(主机)负载的效果。 
 
        切分模式: 垂直(纵向)拆分、水平拆分。
 
垂直拆分
 
        专库专用
 
        一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
        
优点:
        1. 拆分后业务清晰,拆分规则明确。
        2. 系统之间整合或扩展容易。
        3. 数据维护简单。
 
缺点:
        1. 部分业务表无法join,只能通过接口方式解决,提高了系统复杂度。
        2. 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
        3. 事务处理复杂。
 
水平拆分
 
        垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。
 
        相对于垂直拆分,水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中 的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,主要有分表,分库两种模式,如图:
                
 
        
优点:
        1. 不存在单库大数据,高并发的性能瓶颈。
        2. 对应用透明,应用端改造较少。     
        3. 按照合理拆分规则拆分,join操作基本避免跨库。
        4. 提高了系统的稳定性跟负载能力。
 
缺点:
        1. 拆分规则难以抽象。
        2. 分片事务一致性难以解决。
        3. 数据多次扩展难度跟维护量极大。
        4. 跨库join性能较差。
 
拆分的处理难点
 
两张方式共同缺点
 
        1. 引入分布式事务的问题。
        2. 跨节点Join 的问题。
        3. 跨节点合并排序分页问题。
 
针对数据源管理,目前主要有两种思路:
 
        A. 客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个 数据库,在模块内完成数据的整合。 
        优点:相对简单,无性能损耗。   
        缺点:不够通用,数据库连接的处理复杂,对业务不够透明,处理复杂。
 
       B. 通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明;   
        优点:通用,对应用透明,改造少。   
        缺点:实现难度大,有二次转发性能损失。
 
拆分原则
    
        1. 尽量不拆分,架构是进化而来,不是一蹴而就。(SOA)
        2. 最大可能的找到最合适的切分维度。
        3. 由于数据库中间件对数据Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取  尽量少使用多表Join -尽量通过数据冗余,分组避免数据垮库多表join。
        4. 尽量避免分布式事务。
        5. 单表拆分到数据1000万以内。
 
切分方案
    
        范围、枚举、时间、取模、哈希、指定等
 
案例分析
 
场景一
建立一个历史his系统,将公司的一些历史个人游戏数据保存到这个his系统中,主要是写入,还有部分查询,读写比约为1:4;由于是所有数据的历史存取,所以并发要求比较高; 
 
分析:
历史数据
写多都少
越近日期查询越频繁?
什么业务数据?用户游戏数据
有没有大规模分析查询?
数据量多大?
保留多久?
机器资源有多少?
 
方案1:按照日期每月一个分片
带来的问题:1.数据热点问题(压力不均匀)
 
方案2:按照用户取模,  --by Jerome 就这个比较合适了
带来的问题:后续扩容困难
 
方案3:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
 
场景二
建立一个商城订单系统,保存用户订单信息。
 
分析:
电商系统
一号店或京东类?淘宝或天猫?
实时性要求高
存在瞬时压力
基本不存在大规模分析
数据规模?
机器资源有多少?
维度?商品?用户?商户?
 
方案1:按照用户取模,
带来的问题:后续扩容困难
 
方案2:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
 
方案3:按省份地区或者商户取模
数据分配不一定均匀
 
场景3
上海公积金,养老金,社保系统
 
分析:
社保系统
实时性要求不高
不存在瞬时压力
大规模分析?
数据规模大
数据重要不可丢失
偏于查询?
 
方案1:按照用户取模,
带来的问题:后续扩容困难
 
方案2:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
 
方案3:按省份区县地区枚举
数据分配不一定均匀
 
 
 
        数据库问题解决后,应用面对的新挑战就是拆分应用等
 
参考
        Mycat在线视频培训【链接:http://pan.baidu.com/s/1nuR26rZ 密码:1gr9 (2015)】
        大型网站系统与Java中间件实践.pdf
        MySQL数据库的演化可以参考:http://mp.weixin.qq.com/s?__biz=MjM5ODI5Njc2MA==&mid=207666963&idx=2&sn=0d0710e071420c6fc6af8d4a3bc3dfe6&scene=1#rd
        http://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651959773&idx=1&sn=7e4ad0dcd050f6662dfaf39d9de36f2c&chksm=bd2d04018a5a8d17b92098b4840aac23982e32d179cdd957e4c55011f6a08f6bd31f9ba5cfee&mpshare=1&scene=23&srcid=1220t4ttl8wZaYHlQRzO0xYB#rd【一分钟掌握数据库垂直拆分-沈剑】
 
 
垂直拆分
  垂直拆分就是要把表按模块划分到不同数据库表中(当然原则还是不破坏第三范式),这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性
  垂直拆分:单表大数据量依然存在性能瓶颈
  水平拆分
  上面谈到垂直切分只是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。例如像计费系统,通过按时间来划分表就比较合适,因为系统都是处理某一时间段的数据。而像SaaS应用,通过按用户维度来划分数据比较合适,因为用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,简单的按user_id范围来水平切分
  通俗理解:水平拆分行,行数据拆分到不同表中, 垂直拆分列,表数据拆分到不同表中
  垂直与水平联合切分
  由上面可知垂直切分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题,因此常常就会把两者结合使用,这在大型网站里是种常见的策略
  案例:
  以mysql为例,简单购物系统暂设涉及如下表:
  1.产品表(数据量10w,稳定)
  2.订单表(数据量200w,且有增长趋势)
  3.用户表 (数据量100w,且有增长趋势)
  以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万
  垂直拆分:
  解决问题:
  表与表之间的io竞争
  不解决问题:
  单表中数据量增长出现的压力
  方案:
  把产品表和用户表放到一个server上
  订单表单独放到一个server上
  水平拆分:
  解决问题:
  单表中数据量增长出现的压力
  不解决问题:
  表与表之间的io争夺
  方案:
  用户表通过性别拆分为男用户表和女用户表
  订单表通过已完成和完成中拆分为已完成订单和未完成订单
  产品表 未完成订单放一个server上
  已完成订单表盒男用户表放一个server上
  女用户表放一个server上(女的爱购物)
 
 
 
《大型网站系统与Java中间件实践》本书围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。从分布式系统的知识切入,让读者对分布式系统有基本的了解;然后介绍大型网站随着数据量、访问量增长而发生的架构变迁;接着讲述构建 Java 中间件的相关知识;之后的几章都是根据笔者的经验来介绍支撑大型网站架构的 Java 中间件系统的设计和实践。本节为大家介绍专库专用,数据垂直拆分。

AD:
51CTO 网+ 第十二期沙龙:大话数据之美_如何用数据驱动用户体验

2.2.7 读写分离后,数据库又遇到瓶颈

通过读写分离以及在某些场景用分布式存储系统替换关系型数据库的方式,能够降低主库的压力,解决数据存储方面的问题。不过随着业务的发展,我们的主库也会遇到瓶颈。我们的网站演进到现在,交易、商品、用户的数据还都在一个数据库中。尽管采取了增加缓存、读写分离的方式,这个数据库的压力还是在继续增加,因此我们需要去解决这个问题,我们有数据垂直拆分和水平拆分两种选择。

2.2.7.1 专库专用,数据垂直拆分

垂直拆分的意思是把数据库中不同的业务数据拆分到不同的数据库中。结合现在的例子,就是把交易、商品、用户的数据分开,如图2-20 所示。

 



 

这样的变化给我们带来的影响是什么呢?应用需要配置多个数据源,这就增加了所需的配置,不过带来的是每个数据库连接池的隔离。不同业务的数据从原来的一个数据库中拆分到了多个数据库中,那么就需要考虑如何处理原来单机中跨业务的事务。一种办法是使用分布式事务,其性能要明显低于之前的单机事务;而另一种办法就是去掉事务或者不去追求强事务支持,则原来在单库中可以使用的表关联的查询也就需要改变实现了。

对数据进行垂直拆分之后,解决了把所有业务数据放在一个数据库中的压力问题。并且也可以根据不同业务的特点进行更多优化。

 

2.2.7.2 垂直拆分后的单机遇到瓶颈,数据水平拆分

与数据垂直拆分对应的还有数据水平拆分。数据水平拆分就是把同一个表的数据拆到两个数据库中。产生数据水平拆分的原因是某个业务的数据表的数据量或者更新量达到了单个数据库的瓶颈,这时就可以把这个表拆到两个或者多个数据库中。数据水平拆分与读写分离的区别是,读写分离解决的是读压力大的问题,对于数据量大或者更新量的情况并不起作用。数据水平拆分与数据垂直拆分的区别是,垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。例如,经过垂直拆分后,用户表与交易表、商品表不在一个数据库中了,如果数据量或者更新量太大,我们可以进一步把用户表拆分到两个数据库中,它们拥有结构一模一样的用户表,而且每个库中的用户表都只涵盖了一部分的用户,两个数据库的用户合在一起就相当于没有拆分之前的用户表。我们先来简单看一下引入数据水平拆分后的结构,如图2-21 所示。

我们来分析一下水平拆分后给业务应用带来的影响。

首先,访问用户信息的应用系统需要解决SQL 路由的问题,因为现在用户信息分在了两个数据库中,需要在进行数据库操作时了解需要操作的数据在哪里。

此外,主键的处理也会变得不同。原来依赖单个数据库的一些机制需要变化,例如原来使用Oracle 的Sequence 或者MySQL 表上的自增字段的,现在不能简单地继续使用了。并且在不同的数据库中也不能直接使用一些数据库的限制来保证主键不重复了。

 



 

最后,由于同一个业务的数据被拆分到了不同的数据库中,因此一些查询需要从两个数据库中取数据,如果数据量太大而需要分页,就会比较难处理了。

不过,一旦我们能够完成数据的水平拆分,我们将能够很好地应对数据量及写入量增长的情况。具体如何完成数据水平拆分,在后面分布式数据访问层的章节中我们将进行更加详细的介绍。

 

2.2.8 数据库问题解决后,应用面对的新挑战

2.2.8.1 拆分应用

前面所讲的读写分离、分布式存储、数据垂直拆分和数据水平拆分都是在解决数据方面的问题。下面我们来看看应用方面的变化。

之前解决了应用服务器从单机到多机的扩展,应用就可以在一定范围内水平扩展了。随着业务的发展,应用的功能会越来越多,应用也会越来越大。我们需要考虑如何不让应用持续变大,这就需要把应用拆开,从一个应用变为两个甚至多个应用。我们来看两种方式。

第一种方式,根据业务的特性把应用拆开。在我们的例子中,主要的业务功能分为三大部分:交易、商品和用户。我们可以把原来的一个应用拆成分别以交易和商品为主的两个应用,对于交易和商品都会有涉及用户的地方,我们让这两个系统自己完成涉及用户的工作,而类似用户注册、登录等基础的用户工作,可以暂时交给两系统之一来完成(注意,我们在这里主要是通过例子说明拆分的做法),如图2-22 所示,这样的拆分可以使大的应用变小。

 



 

我们还可以按照用户注册、用户登录、用户信息维护等再拆分,使之变成三个系统。不过,这样拆分后在不同系统中会有一些相似的代码,例如用户相关的代码。如何能够保证这部分代码的一致以及如何对其复用是需要解决的问题。此外,按这样的方式拆分出来的新系统之间一般没有直接的相互调用。而且,新拆出来的应用可能会连接同样的数据库。

来看一个具体的例子,如图2-23 所示。

我们根据业务的不同功能拆分了几个业务应用,而且这些业务应用之间不存在直接的调用,它们都依赖底层的数据库、缓存、文件系统、搜索等。这样的应用拆分确实能够解决当下的一些问题,不过也有一些缺点。  



 

 

2.2.8.2 走服务化的路

我们再来看一下服务化的做法。图2-24 是一个示意图。从中可以看到我们把应用分为了三层,处于最上端的是Web 系统,用于完成不同的业务功能;处于中间的是一些服务中心,不同的服务中心提供不同的业务服务;处于下层的则是业务的数据库。当然,我们在这个图中省去了缓存等基础的系统,因此可以说是服务化系统结构的一个简图。

 



 

图2-24 与之前的图相比有几个很重要的变化。首先,业务功能之间的访问不仅是单机内部的方法调用了,还引入了远程的服务调用。其次,共享的代码不再是散落在不同的应用中了,这些实现被放在了各个服务中心。第三,数据库的连接也发生了一些变化,我们把与数据库的交互工作放到了服务中心,让前端的Web 应用更加注重与浏览器交互的工作,而不必过多关注业务逻辑的事情。连接数据库的任务交给相应的业务服务中心了,这样可以降低数据库的连接数。而服务中心不仅把一些可以共用的之前散落在各个业务的代码集中了起来,并且能够使这些代码得到更好的维护。第四,通过服务化,无论是前端Web 应用还是服务中心,都可以是由固定小团队来维护的系统,这样能够更好地保持稳定性,并能更好地控制系统本身的发展,况且稳定的服务中心日常发布的次数也远小于前端Web 应用,因此这个方式也减小了不稳定的风险。

要做到服务化还需要一些基础组件的支撑,在后面服务框架的章节我们会具体介绍。

 

 

通过某种特定的条件,将存放在同一个数据库中的数据分散存放到多个数据库上,实现分布存储,通过路由规则路由访问特定的数据库,这样一来每次访问面对的就不是单台服务器了,而是N台服务器,这样就可以降低单台机器的负载压力。提示:sqlserver 2005版本之后,可以友好的支持“表分区”。

  垂直(纵向)拆分:是指按功能模块拆分,比如分为订单库、商品库、用户库...这种方式多个数据库之间的表结构不同。

  水平(横向)拆分:将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。

SQL Server:数据库/数据表 拆分
▲(纵向拆分)

SQL Server:数据库/数据表 拆分
▲(横向拆分)

  1,实现原理:使用垂直拆分,主要要看应用类型是否合适这种拆分方式,如系统可以分为,订单系统,商品管理系统,用户管理系统业务系统比较明的,垂直拆分能很好的起到分散数据库压力的作用。业务模块不明晰,耦合(表关联)度比较高的系统不适合使用这种拆分方式。但是垂直拆分方式并不能彻底解决所有压力问题,例如 有一个5000w的订单表,操作起来订单库的压力仍然很大,如我们需要在这个表中增加(insert)一条新的数据,insert完毕后,数据库会针对这张表重新建立索引,5000w行数据建立索引的系统开销还是不容忽视的,反过来,假如我们将这个表分成100个table呢,从table_001一直到table_100,5000w行数据平均下来,每个子表里边就只有50万行数据,这时候我们向一张只有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,极大了提高了DB的运行时效率,提高了DB的并发量,这种拆分就是横向拆分

  2,实现方法:垂直拆分,拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。横向拆分的规则很多,这里总结前人的几点,

  (1)顺序拆分:如可以按订单的日前按年份才分,2003年的放在db1中,2004年的db2,以此类推。当然也可以按主键标准拆分。

  优点:可部分迁移

  缺点:数据分布不均,可能2003年的订单有100W,2008年的有500W。

  (2)hash取模分: 对user_id进行hash(或者如果user_id是数值型的话直接使用user_id的值也可),然后用一个特定的数字,比如应用中需要将一个数据库切分成4个数据库的话,我们就用4这个数字对user_id的hash值进行取模运算,也就是user_id%4,这样的话每次运算就有四种可能:结果为1的时候对应DB1;结果为2的时候对应DB2;结果为3的时候对应DB3;结果为0的时候对应DB4,这样一来就非常均匀的将数据分配到4个DB中。

  优点:数据分布均匀

  缺点:数据迁移的时候麻烦;不能按照机器性能分摊数据 。

  (3)在认证库中保存数据库配置

  就是建立一个DB,这个DB单独保存user_id到DB的映射关系,每次访问数据库的时候都要先查询一次这个数据库,以得到具体的DB信息,然后才能进行我们需要的查询操作。

  优点:灵活性强,一对一关系

  缺点:每次查询之前都要多一次查询,会造成一定的性能损失。

 ------------------------------------------------------------------------------
mysql数据库的水平拆分与垂直拆分
近端时间在面试,发现很多面试官或者面试都把数据的水平拆分合垂直拆分给搞混了,今天特意写了一篇博客来说说水平拆分和垂直拆分希望对程序猿们有所帮助。

数据库水平与垂直拆分:

垂直(纵向)拆分:是指按功能模块拆分,比如分为订单库、商品库、用户库...这种方式多个数据库之间的表结构不同。

水平(横向)拆分:将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。

数据表的水平与垂直拆分:

垂直拆分:按字段功能主次拆分,比如最常见的商品表、商品图片表、商品详细信息...表与表之间的结构不同

水平拆分:同数据库的水平拆分原理一样主要是将数据进行拆分保存到不同的表当中,这些表的结构完全相同。、

 

使用用垂直拆分要主要看系统是否适合这种拆分方式,如系统可分为用户系统,商品系统、订单系统等这些业务比较明确的比较适合使用垂直拆分,垂直拆分能很好分散数据库压力。业务模块不清晰,模块耦合度较高的系统并不适合垂直拆分。垂直拆分并不能彻底解决所有的压力问题,例如有一张8000w的订单表而且订单随着时间还在一直增加,操作起这张订单表压力依然很大,如我们需要在这个表中增加(insert)一条新的数据,insert完毕后,数据库会针对这张表重新建立索引,8000w行数据建立索引的系统开销还是不容忽视的,这类情况就可以使用到水平拆分了,可以将表分成100个table,table_001一直到table_100,8000w数据平均分下来就是80万的数据(经过实际测试mysql数据量达到400w的时候性能明显降低,故而应将单个mysql的数据量控制在300W以内),这时候我们向一张只有80w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,极大了提高了DB的运行时效率,提高了DB的并发量,这种拆分就是水平(横向)拆分

数据库拆的实现方式:

垂直拆分,拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了这里不多讲。横向拆分的规则很多,这里总结了以下几点:

1、顺序拆分:例如订单表可以按订单的日期按年份才分,2016年的放在db1中,2017年的db2,以此类推。当然也可以按主键标准拆分。

      优点:可部分迁移

      缺点:数据分布不均,可能2016年的订单有200W,2017年的有800W。

2、hash取模分: 例如订单表对user_id进行hash(或者如果user_id是数值型的话直接使用user_id的值也可),然后用一个特定的数字,比如应用中需要将一个数据库切分成4个数据库的话,我们就用4这个数字对user_id的hash值进行取模运算,也就是user_id%4,这样的话每次运算就有四种可能:结果为1的时候对应DB1;结果为2的时候对应DB2;结果为3的时候对应DB3;结果为0的时候对应DB4,这样一来就非常均匀的将数据分配到4个DB中。

  优点:数据分布均匀

  缺点:数据迁移的时候麻烦;不能按照机器性能分摊数据 。

3、在认证库中保存数据库配置,就是建立一个DB,这个DB单独保存user_id到DB的映射关系,每次访问数据库的时候都要先查询一次这个数据库,以得到具体的DB信息,然后才能进行我们需要的查询操作。

  优点:灵活性强,一对一关系

  缺点:每次查询之前都要多一次查询,会造成一定的性能损失。

ps:暂时只想到这些希望对大家有帮助,如果有更好的方法欢迎留言区评论交流
----------------------------------------------------------------------------

oracle表分区

oracle表分区详解(按天、按月、按年等)


分区表的概念:
  
当表中的数据量不断增大,查询数据的速度就会变慢,应用程序的性能就会下降,这时就应该考虑对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据在物理上存放到多个表空间(物理文件上),这样查询数据时,不至于每次都扫描整张表。


分区表的优点:


1)   改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度。


2)   增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用;


3)   维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可;


4)   均衡I/O:可以把不同的分区映射到磁盘以平衡I/O,改善整个系统性能。


分区表的种类:


1.范围分区
概念: 范围分区将数据基于范围映射到每一个分区,这个范围是你在创建分区时指定的分区键决定的。这种分区方式是最为常用的,并且分区键经常采用日期。举个例子:你可能会将销售数据按照月份进行分区。




-- 按行分区
SQL> CREATE TABLE part_andy1
  2  (
  3      andy_ID NUMBER NOT NULL PRIMARY KEY,
  4      FIRST_NAME  VARCHAR2(30) NOT NULL,
  5      LAST_NAME   VARCHAR2(30) NOT NULL,
  6      PHONE        VARCHAR2(15) NOT NULL,
  7      EMAIL        VARCHAR2(80),
  8      STATUS       CHAR(1)
  9  )
 10  PARTITION BY RANGE (andy_ID)
 11  (
 12      PARTITION PART1 VALUES LESS THAN (10000) ,
 13      PARTITION PART2 VALUES LESS THAN (20000)
 14  );


Table created.


-- 按时间分区


SQL> CREATE TABLE part_andy2
  2  (
  3  ORDER_ID      NUMBER(7) NOT NULL,
  4  ORDER_DATE    DATE,
  5  OTAL_AMOUNT NUMBER,
  6  CUSTOTMER_ID NUMBER(7),
  7  PAID           CHAR(1)
  8  )
  9  PARTITION BY RANGE (ORDER_DATE)
 10  (
 11    PARTITION p1 VALUES LESS THAN (TO_DATE('2014-10-1', 'yyyy-mm-dd')) ,
 12    PARTITION p2 VALUES LESS THAN (TO_DATE('2015-10-1', 'yyyy-mm-dd')) ,
 13    PARTITION p3 VALUES LESS THAN (TO_DATE('2016-10-1', 'yyyy-mm-dd')) ,
 14    partition p4 values less than (maxvalue)
 15  );


Table created.


2.  Hash分区


概念:
对于那些无法有效划分范围的表,可以使用hash分区,这样对于提高性能还是会有一定的帮助。hash分区会将表中的数据平均分配到你指定的几个分区中,列所在分区是依据分区列的hash值自动分配,因此你并不能控制也不知道哪条记录会被放到哪个分区中,hash分区也可以支持多个依赖列。


注意:
hash分区最主要的机制是根据hash算法来计算具体某条纪录应该插入到哪个分区中,hash算法中最重要的是hash函数,Oracle中如果你要使用hash分区,只需指定分区的数量即可。建议分区的数量采用2的n次方,这样可以使得各个分区间数据分布更加均匀。


--按hash分区
SQL> create table part_andy3
  2  (
  3  transaction_id number primary key,
  4  item_id number(8) not null
  5  )
  6  partition by hash(transaction_id)
  7  (
  8  partition part_01 ,
  9  partition part_02 ,
 10  partition part_03
 11  );


Table created.


3.  List分区


概念:
List分区也需要指定列的值,其分区值必须明确指定,该分区列只能有一个,不能像range或者hash分区那样同时指定多个列做为分区依赖列,但它的单个分区对应值可以是多个。


注意:
在分区时必须确定分区列可能存在的值,一旦插入的列值不在分区范围内,则插入/更新就会失败,因此通常建议使用list分区时,要创建一个default分区存储那些不在指定范围内的记录,类似range分区中的maxvalue分区。


-- 按list分区
SQL> create table part_andy4
  2  (
  3  id varchar2(15 byte) not null,
  4  city varchar2(20)
  5  )
  6  partition by list (city)
  7  (
  8  partition t_list025 values ('beijing'),
  9  partition t_list372 values ('shanghai') ,
 10  partition t_list510 values ('changsha'),
 11  partition p_other values (default)
 12  );


Table created.


4. 组合分区


Oracle10g提供两种分区组合
– Range-hash
SQL> create table part_andy5
  2  (
  3  transaction_id number primary key,
  4  item_id number(8) not null,
  5  item_description varchar2(300),
  6  transaction_date date
  7  )
  8  partition by range(transaction_date)subpartition by hash(transaction_id)
  9  (
 10  partition part_01 values less than(TO_DATE('2014-10-1', 'yyyy-mm-dd')),
 11  partition part_02 values less than(TO_DATE('2015-10-1', 'yyyy-mm-dd')),
 12  partition part_03 values less than(maxvalue)
 13  );


Table created.






– Range-list
SQL> CREATE TABLE SALES
  2  (
  3  PRODUCT_ID VARCHAR2(5),
  4  SALES_DATE DATE,
  5  SALES_COST NUMBER(10),
  6  STATUS VARCHAR2(20)
  7  )
  8  PARTITION BY RANGE(SALES_DATE) SUBPARTITION BY LIST (STATUS)
  9  (
 10  PARTITION P1 VALUES LESS THAN(TO_DATE('2014-10-1', 'yyyy-mm-dd'))
 11  (SUBPARTITION P1SUB1 VALUES ('ACTIVE') ,SUBPARTITION P1SUB2 VALUES ('INACTIVE')
 12  ),PARTITION P2 VALUES LESS THAN (TO_DATE('2015-10-1', 'yyyy-mm-dd'))
 13  (
 14  SUBPARTITION P2SUB1 VALUES ('ACTIVE') ,
 15  SUBPARTITION P2SUB2 VALUES ('INACTIVE')
 16  )
 17  );


Table created.




 Oracle11g增加了四种组合
– RANGE-RANGE
– LIST-RANGE
– LIST-HASH
– LIST-LIST 


Oracle 11g 中虚拟列来实现。在11g之前 分区表的partition key必须是物理存在的。11g开始提供了虚拟列,并且可以作为partition key 。
--按星期分区 
SQL> CREATE TABLE part_andy6
  2  (
  3  getdate   date NOT NULL,
  4  wd        NUMBER GENERATED ALWAYS AS (TO_NUMBER (TO_CHAR (getdate, 'D'))) VIRTUAL
  5  )
  6  PARTITION BY LIST (wd)
  7  (
  8  PARTITION Mon  VALUES (1),
  9  PARTITION Tue  VALUES (2),
 10  PARTITION Wed  VALUES (3),
 11  PARTITION Thu  VALUES (4),
 12  PARTITION Fri  VALUES (5),
 13  PARTITION Sat   VALUES (6),
 14  PARTITION Sun  VALUES (7)
 15  );


Table created.


SQL>
SQL> insert into part_andy6(getdate) values(sysdate);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-1);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-2);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-3);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-4);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-5);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-6);


1 row created.


SQL> insert into part_andy6(getdate) values(sysdate-7);


1 row created.


-- 检查测试成功
SQL> select * from part_andy6;


GETDATE                     WD
------------------- ----------
2014-11-23 16:35:07          1
2014-11-24 16:35:07          2
2014-11-25 16:35:07          3
2014-11-26 16:35:07          4
2014-11-27 16:35:07          5
2014-11-28 16:35:07          6
2014-11-29 16:35:07          7
2014-11-22 16:35:08          7


8 rows selected.


Oracle Database 11g,Interval类型分区表,可以根据加载数据,自动创建指定间隔的分区。


创建按月分区的分区表:


a. 创建分区表


SQL> CREATE TABLE interval_andy7 (a1 NUMBER, a2 DATE)
  2  PARTITION BY RANGE (a2)
  3  INTERVAL ( NUMTOYMINTERVAL (1, 'MONTH') )
  4  (PARTITION part1
  5  VALUES LESS THAN (TO_DATE('2014-11-1', 'yyyy-mm-dd')),
  6  PARTITION part2
  7  VALUES LESS THAN (TO_DATE('2014-12-1', 'yyyy-mm-dd'))
  8  );


Table created.
注意:如果在建Interval分区表是没有把所有的分区写完成,在插入相关数据后会自动生成分区
b. 查看现在表的分区:
SQL> select table_name,partition_name from user_tab_partitions where table_name='INTERVAL_ANDY7';
TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVALPART                   PART1
INTERVALPART                   PART2
c.  插入测试数据:
SQL> begin
  2  for i in 0 .. 11 loop
  3  insert into interval_andy7 values(i,add_months(to_date('2014-11-1','yyyy-mm-dd'),i));
  4  end loop ;
  5  commit;
  6  end;
  7  /


PL/SQL procedure successfully completed.


PL/SQL 过程已成功完成。
补充:add_months()函数获取前一个月或者下一个月的月份, 参数中 负数 代表 往前, 正数 代表 往后。
--上一个月
select to_char(add_months(trunc(sysdate),-1),'yyyymm') from dual;
--下一个月 
select to_char(add_months(trunc(sysdate),1),'yyyymm') from dual;
d. 观察自动创建的分区:
SQL> select table_name,partition_name from user_tab_partitions where table_name='INTERVAL_ANDY7';


TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVAL_ANDY7                 PART1
INTERVAL_ANDY7                 PART2
INTERVAL_ANDY7                 SYS_P24
INTERVAL_ANDY7                 SYS_P25
INTERVAL_ANDY7                 SYS_P26
INTERVAL_ANDY7                 SYS_P27
INTERVAL_ANDY7                 SYS_P28
INTERVAL_ANDY7                 SYS_P29
INTERVAL_ANDY7                 SYS_P30
INTERVAL_ANDY7                 SYS_P31
INTERVAL_ANDY7                 SYS_P32


TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVAL_ANDY7                 SYS_P33
INTERVAL_ANDY7                 SYS_P34


13 rows selected.








下面创建一个以天为间隔的分区表:


1. 创建分区表:
SQL> create table interval_andy8
  2  (
  3  id    number,
  4  dt    date
  5  )
  6  partition by range (dt)
  7  INTERVAL (NUMTODSINTERVAL(1,'day'))
  8  (
  9  partition p20141101 values less than (to_date('2014-11-1','yyyy-mm-dd'))
 10  );


Table created.


2. 查看表分区:
SQL> select table_name,partition_name from user_tab_partitions where table_name='INTERVAL_ANDY8';


TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVAL_ANDY8                 P20141101


3. 插入测试数据:
begin
for i in 1 .. 12 loop
insert into INTERVAL_ANDY8 values(i,trunc(to_date('2014-11-1','yyyy-mm-dd')+i));
end loop;
commit;
end;
/
PL/SQL 过程已成功完成。
4. 观察自动创建的分区:
SQL> select table_name,partition_name from user_tab_partitions where table_name='INTERVAL_ANDY8';


TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVAL_ANDY8                 P20141101
INTERVAL_ANDY8                 SYS_P35
INTERVAL_ANDY8                 SYS_P36
INTERVAL_ANDY8                 SYS_P37
INTERVAL_ANDY8                 SYS_P38
INTERVAL_ANDY8                 SYS_P39
INTERVAL_ANDY8                 SYS_P40
INTERVAL_ANDY8                 SYS_P41
INTERVAL_ANDY8                 SYS_P42
INTERVAL_ANDY8                 SYS_P43
INTERVAL_ANDY8                 SYS_P44


TABLE_NAME                     PARTITION_NAME
------------------------------ ------------------------------
INTERVAL_ANDY8                 SYS_P45
INTERVAL_ANDY8                 SYS_P46


13 rows selected.
--------------------------------------------------------------------------------------
oracle分区技术提高查询效率
概述:
当表中的数据量不断增大,查询数据的速度就会变慢,应用程序的性能就会下降,这时就应该考虑对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据在物理上存放到多个表空间(物理文件上),这样查询数据时,不至于每次都扫描整张表。

下面介绍如何使用分区增加查询效率

range分区:就是区域分区

复制代码
CREATE TABLE SALE
(
    PRODUCT_ID VARCHAR2(5),
    SALE_COUNT NUMBER(10,2)
)
PARTITION BY RANGE (SALE_COUNT)
(
    PARTITION P1 VALUES LESS THAN (1000) TABLESPACE CUS_TS01,
    PARTITION P2 VALUES LESS THAN (2000) TABLESPACE CUS_TS02
)
复制代码
查看分区语法:

  select * from user_tab_partitions;  --查询所有分区情况,可以接条件where table_name='sale'查看分区表结构

  select * from sale partition(p1);  --查询某表的某一分区数据

分区后,新增数据的SALE_COUNT字段如果小于1000就存储到P1分区中,如果1000到2000存储到P2分区中。

但是这时如果我们新增的一条数据的SALE_COUNT字段值大于2000,将无法存储到表中。

我们可以扩展分区,语法如下:

  alter table sale add partition p4 values less than(maxvalue); --大于2000的都会存到此分区中,当然也可以增加更多的分区

同时可以删除分区,语法如下:

  alter table sale drop partition p4; --注意:删除分区会把分区内已有的数据同时删除

但还存在一个问题,如果现在update分区p1中的SALE_COUNT值为1500,是不会成功的,需要在update前增加以下语句:

  alter table sale enable row movement; --使其row能移动

这样再update就可以成功了

分区索引
分区之后虽然可以提高查询的效率,但也仅仅是提高了数据的范围,所以我们在有必要的情况下,需要建立分区索引,从而进一步提高效率。

分区索引大体上分为两大类,一类叫做local,一类叫做global。

local:在每个分区上建立索引(一般采用这种方式)

global:一种在全局上建立索引,这种方式分不分区都一样,一般不使用

下面进行语法演示:

注意:分区上建立的索引一定是分区字段

create index idx_count on sale(sale_count) local;--建立分区索引,在sale表的每个分区都建立了索引

select * from user_ind_partitions;--查询所有分区索引情况

全局索引global写法就是把上面的local替换成global,但不会使用

 

 

有些时候,如果你分区分为0~1000,1000~2000,这时如果说0~1500这个范围内的数据会被频繁查询,1500之后查询很少,那么就可以使用这种自定义的全局索引方式对0~1500建立索引,之后的设置maxvalue即可,语法与分区语法相似

global自定义全局索引方式(前缀索引):

create index idxname on table(field) global

  partition by range(field) 

  (

    partition p1 values less than(value),  .......

    partition pN values less than(maxvalue)

  );

 

其他分区介绍
1.hash分区

hash分区实现均匀的负载值分配,增加hash分区可以重新分布数据,简单理解就是分区直接平均分配

复制代码
CREATE TABLE SALE
(
    PRODUCT_ID VARCHAR2(5),
    SALE_COUNT NUMBER(10,2)
)
PARTITION BY HASH (PRODUCT_ID)
(
    PARTITION P1,
    PARTITION P2 
)
复制代码
2.list分区

该分区的特点是某列的值只有几个,基于这样的特点我们可以采用列表分区。

复制代码
CREATE  TABLE  ListTable
(
    id    INT  PRIMARY  KEY ,
    name  VARCHAR (20),
    area  VARCHAR (10)
)
PARTITION  BY  LIST (area)
(
    PARTITION  part1 VALUES ('guangdong','beijing') TABLESPACE  Part1_tb,
    PARTITION  part2 VALUES ('shanghai','nanjing')  TABLESPACE  Part2_tb
);
复制代码
3.复合分区(用的不多)

复制代码
create table student(
    sno number,
    sname varchar2(10)
)
partition by range (sno)
subpartition by hash (sname)
subpartitions 4
(
    partition p1 values less than(1000),
    partition p2 values less than(2000),
    partition p3 values less than(maxvalue)
);
复制代码
复合分区首先大体上分为三个分区p1,p2,p3,然后每一个分区内部再进行hash分区,分为4份

查询子分区的语句:select * from user_tab_subpartitions where table_name='student';

4.间隔分区(工作中常用)

是一种分区自动化的分区,可以指定时间间隔进行分区,这是oracle11g的新特性,实际工作中很常用。实际上是由range分区引申的,最终实现了range分区的自动化

复制代码
create table interval_sale
(sid int,sdate timestamp)
partition by range(sdate)
interval (numtoyminterval(1,'MONTH'))
(
partition p1 values less than (TIMESTAMP '2017-11-12 00:00:00.00')
)
复制代码
指定时间之前建立一个分区,之后每隔一个月建立一个分区

问题:如果我们drop掉了表,那么这个表的分区还存在吗?

答案是存在的,oracle提供了很强大的数据恢复功能,有一个类似回收站的机制,删除表后,分区以特殊的形式仍然存在user_tab_partitions中,使用purge recyclebin语法后,会清空回收站,使用flashback table 表名 to before drop语句可以恢复删除的表。
-------------------------------------------------------------------------------------
当大家遇到小数据的时候,大家可能想着在where后面多加点约束条件;当数据在大点的时候,大家可能就开始考虑给这添加索引了;当大家遇到百万级千万级数据的时候,大家就可能开始添加表分区了。

so,我就开始了我的分(keng)区(die)之旅。

一开始,由于我的这张大表有100多万数据量,就单单一个存储过程居然执行了一个多钟,于是,我便把这张大表改成分区表,并在ID上给这添加索引,优化存储过程。改成分区表之后,效果果然斐然,从1个多钟优化到了一分半钟。可惜啊,好景不长,才过了2天,测试姐姐就把我啦过去看问题,说我的存储过程把整个过程卡住了,700多万的数据,存储过程居然执行了将近4个小时没执行完,我嘴上笑嘻嘻,心里妈卖批。自己挖的坑,含着泪也要把它搞定。



我首先查了下是不是分区表的索引失效了,但经过排查,发现并不是(心中一万头草拟马跑过),然后,我就执行一乐该id下的700多万数据的存储过程,果然很慢,难怪他被卡死了,一查下去发现是select语句查询很慢,奇怪点就在当我用另一个也是拥有700多万的id数据执行存储过程的时候,奇迹发现了,居然3分多钟就完成。



为什么同样是700多万的数据,一个执行了4个多钟还没完成,一个执行了3分多钟就完成了。

抱着疑问,我一步一步排查这个分区表下不同id表有什么区别呢,最终发现执行快的id,在该分区表下global_stats=YES,而另一个的global_stats则=false。

于是我查到了以下的信息

"分区表里global_stats=YES的全局统计信息是否准确关系到optimizer能否选择较优的执行计划,对分区表执行全局统计会不可避免的产生FTS加重系统负担,尤其对于DW环境里规模较大的分区表而言更是如此。"



而global_stats这个代表该分区表是否对该分区进行了统计分析,一般情况下,系统在不知道什么时候都会对这分区就行统计分析的。而对这个分区进行统计分析又包含了两种方式,非incremental方式,incremental方式,而非incremental方式会进行全量扫描分区表中所有的分区,incremental方式则会增量扫描分区表中的分区。



incremental statistics collect正是在这一背景下应运而生,简单的说incremental statistics collect会实时记录分区表里每个partition每列值的更新情况,这一信息保存在SYSAUX表空间里,后续根据这一信息在执行全局统计时仅会针对有变化的partition进行statistics collect,并将收集的结果与没有变化过的partition原有的统计信息进行整合,计算出准确的global stats,省去了必须去扫描每一个partition的步骤。



非incremental方式下新加分区后对整个分区表收集统计信息,会全量扫描分区表中所有的分区,即使那些没有改变过的分区也会被重新扫描一遍
------------------------------------------------------------------------
深入学习Oracle分区表及分区索引
 

关于分区表和分区索引(About Partitioned Tables and Indexes)对于10gR2而言,基本上可以分成几类:

?          Range(范围)分区

?          Hash(哈希)分区

?          List(列表)分区

?          以及组合分区:Range-Hash,Range-List。

  对于表而言(常规意义上的堆组织表),上述分区形式都可以应用(甚至可以对某个分区指定compress属性),只不过分区依赖列不能是lob,long之类数据类型,每个表的分区或子分区数的总数不能超过1023个。

  对于索引组织表,只能够支持普通分区方式,不支持组合分区,常规表的限制对于索引组织表同样有效,除此之外呢,还有一些其实的限制,比如要求索引组织表的分区依赖列必须是主键才可以等。

  注:本篇所有示例仅针对常规表,即堆组织表!

  对于索引,需要区分创建的是全局索引,或本地索引:

  l 全局索引(global index):即可以分区,也可以不分区。即可以建range分区,也可以建hash分区,即可建于分区表,又可创建于非分区表上,就是说,全局索引是完全独立的,因此它也需要我们更多的维护操作。

  l 本地索引(local index):其分区形式与表的分区完全相同,依赖列相同,存储属性也相同。对于本地索引,其索引分区的维护自动进行,就是说你add/drop/split/truncate表的分区时,本地索引会自动维护其索引分区。

Oracle建议如果单个表超过2G就最好对其进行分区,对于大表创建分区的好处是显而易见的,这里不多论述why,而将重点放在when以及how。

ORACLE对于分区表方式其实就是将表分段存储,一般普通表格是一个段存 储,而分区表会分成多个段,所以查找数据过程都是先定位根据查询条件定位分区范围,即数据在那个分区或那几个内部,然后在分区内部去查找数据,一个分区一 般保证四十多万条数据就比较正常了,但是分区表并非乱建立,而其维护性也相对较为复杂一点,而索引的创建也是有点讲究的,这些以下尽量阐述详细即可。

range分区方式,也算是最常用的分区方式,其通过某字段或几个字段的组合的值,从小到大,按照指定的范围说明进行分区,我们在INSERT数据的时候就会存储到指定的分区中。

List分区方式,一般是在range基础上做的二级分区较多,是一种列举方式进行分区,一般讲某些地区、状态或指定规则的编码等进行划分。

Hash分区方式,它没有固定的规则,由ORACLE管理,只需要将值INSERT进去,ORACLE会自动去根据一套HASH算法去划分分区,只需要告诉ORACLE要分几个区即可。

WHEN
  一、When使用Range分区

  Range分区呢是应用范围比较广的表分区方式,它是以列的值的范围来做为分区的划分条件,将记录存放到列值所在的range分区中,比如按照 时间划分,2008年1季度的数据放到a分区,08年2季度的数据放到b分区,因此在创建的时候呢,需要你指定基于的列,以及分区的范围值,如果某些记录 暂无法预测范围,可以创建maxvalue分区,所有不在指定范围内的记录都会被存储到maxvalue所在分区中,并且支持指定多列做为依赖列,后面在 讲how的时候会详细谈到。

  二、When使用Hash分区

  通常呢,对于那些无法有效划分范围的表,可以使用hash分区,这样对于提高性能还是会有一定的帮助。hash分区会将表中的数据平均分配到你 指定的几个分区中,列所在分区是依据分区列的hash值自动分配,因此你并不能控制也不知道哪条记录会被放到哪个分区中,hash分区也可以支持多个依赖 列。

  三、When使用List分区

  List分区与range分区和hash分区都有类似之处,该分区与range分区类似的是也需要你指定列的值,但这又不同与range分区的 范围式列值---其分区值必须明确指定,也不同与hash分区---通过明确指定分区值,你能控制记录存储在哪个分区。它的分区列只能有一个,而不能像 range或者hash分区那样同时指定多个列做为分区依赖列,不过呢,它的单个分区对应值可以是多个。

  你在分区时必须确定分区列可能存在的值,一旦插入的列值不在分区范围内,则插入/更新就会失败,因此通常建议使用list分区时,要创建一个default分区存储那些不在指定范围内的记录,类似range分区中的maxvalue分区。

  四、When使用组合分区

  如果某表按照某列分区之后,仍然较大,或者是一些其它的需求,还可以通过分区内再建子分区的方式将分区再分区,即组合分区的方式。

  组合分区呢在10g中有两种:range-hash,range-list。注意顺序哟,根分区只能是range分区,子分区可以是hash分区或list分区。

提示:11g在组合分区功能这块有所增强,又推出了range-range,list-range,list-list,list-hash, 这就相当于除hash外三种分区方式的笛卡尔形式都有了。为什么会没有hash做为根分区的组合分区形式呢,再仔细回味一下第二点,你一定能够想明 白~~。

深入学习Oracle分区表及分区索引(2)

一、如何创建
  如果想对某个表做分区,必须在创建表时就指定分区,我们可以对一个包含分区的表中的分区做修改,但不能直接将一个未分区的表修改成分区表(起码在10g是不行的,当然你可能会说,可以通过在线重定义的方式,但是这不是直接哟,这也是借助临时表间接实现的)。

  创建表或索引的语法就不说了,大家肯定比我还熟悉,而想在建表(索引)同时指定分区也非常容易,只需要把创建分区的子句放到";"前就行啦,同 时需要注意表的row movement属性,它用来控制是否允许修改列值所造成的记录移动至其它分区存储,有enable|disable两种状态,默认是disable row movement,当disable时,如果记录要被更新至其它分区,则更新语句会报错。

  下面分别演示不同分区方式的表和索引的创建:

1、创建range分区
  语法如下,需要我们指定的有:

  l column:分区依赖列(如果是多个,以逗号分隔);

  l partition:分区名称;

  l values less than:后跟分区范围值(如果依赖列有多个,范围对应值也应是多个,中间以逗号分隔);

  l tablespace_clause:分区的存储属性,例如所在表空间等属性(可为空),默认继承基表所在表空间的属性。

① 创建一个标准的range分区表:
  JSSWEB> create table t_partition_range (id number,name varchar2(50))

    partition by range(id)(

    partition t_range_p1 values less than (10) tablespace tbspart01,

    partition t_range_p2 values less than (20) tablespace tbspart02,

    partition t_range_p3 values less than (30) tablespace tbspart03,

    partition t_range_pmax values less than (maxvalue) tablespace tbspart04

    );

  表已创建。

 要查询创建分区的信息,可以通过查询user_part_tables,user_tab_partitions两个数据字典(索引分区、组织分区等信息也有对应的数据字典,后续示例会逐步提及)。

  user_part_tables:记录分区的表的信息;

  user_tab_partitions:记录表的分区的信息。

  例如:

JSSWEB> select table_name,partitioning_type,partition_count

        From user_part_tables where table_name='T_PARTITION_RANGE';

JSSWEB> select partition_name,high_value,tablespace_name

       from user_tab_partitions where table_name='T_PARTITION_RANGE'

       order by partition_position;

 

② 创建global索引range分区:
  JSSWEB> create index idx_parti_range_id on t_partition_range(id)

  2 global partition by range(id)(

  3 partition i_range_p1 values less than (10) tablespace tbspart01,

  4 partition i_range_p2 values less than (40) tablespace tbspart02,

  5 partition i_range_pmax values less than (maxvalue) tablespace tbspart03);

  索引已创建。

  由上例可以看出,创建global索引的分区与创建表的分区语句格式完全相同,而且其分区形式与索引所在表的分区形式没有关联关系。

  注意:我们这里借助上面的表t_partition_range来演示创建range分区的global索引,并不表示range分区的表,只能创建range分区的global索引,只要你想,也可以为其创建hash分区的global索引。

  查询索引的分区信息可以通过user_part_indexes、user_ind_partitions两个数据字典:

JSSWEB> select index_name, partitioning_type, partition_count

  2    From user_part_indexes

  3   where index_name = 'IDX_PARTI_RANGE_ID';

 

③ Local分区索引的创建最简单,例如:
  仍然借助t_partition_range表来创建索引

  --首先删除之前创建的global索引

  JSSWEB> drop index IDX_PARTI_RANGE_ID;

  索引已删除。

  JSSWEB> create index IDX_PARTI_RANGE_ID on T_PARTITION_RANGE(id) local;

  索引已创建。

  查询相关数据字典:

JSSWEB> select index_name, partitioning_type, partition_count

  2    From user_part_indexes

  3   where index_name = 'IDX_PARTI_RANGE_ID';

 

JSSWEB> select partition_name, high_value, tablespace_name

  2    from user_ind_partitions

  3   where index_name = 'IDX_PARTI_RANGE_ID'

  4   order by partition_position;

 

 可以看出,local索引的分区完全继承表的分区的属性,包括分区类型,分区的范围值即不需指定也不能更改,这就是前面说的:local索引的分区维护完全依赖于其索引所在表。

  不过呢分区名称,以及分区所在表空间等信息是可以自定义的,例如:

  SQL> create index IDX_PART_RANGE_ID ON T_PARTITION_RANGE(id) local (

  2 partition i_range_p1 tablespace tbspart01,

  3 partition i_range_p2 tablespace tbspart01,

  4 partition i_range_p3 tablespace tbspart02,

  5 partition i_range_pmax tablespace tbspart02

  6 );

  索引已创建。

SQL> select index_name, partitioning_type, partition_count

  2   From user_part_indexes

  3  where index_name = 'IDX_PART_RANGE_ID';

SQL> select partition_name, high_value, tablespace_name

  2   from user_ind_partitions

  3  where index_name = 'IDX_PART_RANGE_ID'

  4  order by partition_position;

 创建hash分区

  语法如下:[图:hash_partitioning.gif]



  语法看起来比range复杂,其实使用起来比range更简单,这里需要我们指定的有:

  l column:分区依赖列(支持多个,中间以逗号分隔);

  l partition:指定分区,有两种方式:

  n 直接指定分区名,分区所在表空间等信息

n 只指定分区数量,和可供使用的表空间。

 

2、创建hash分区
  JSSWEB> create table t_partition_hash (id number,name varchar2(50))

  2 partition by hash(id)(

  3 partition t_hash_p1 tablespace tbspart01,

  4 partition t_hash_p2 tablespace tbspart02,

  5 partition t_hash_p3 tablespace tbspart03);

  表已创建。

  要实现同样效果,你还可以这样:

  JSSWEB> create table t_partition_hash2 (id number,name varchar2(50))

  2 partition by hash(id)

  3 partitions 3 store in(tbspart01,tbspart02,tbspart03);

  表已创建。

 这就是上面说的,直接指定分区数量和可供使用的表空间。

  提示:这里分区数量和可供使用的表空间数量之间没有直接对应关系。分区数并不一定要等于表空间数。

要查询表的分区信息,仍然是通过user_part_tables,user_tab_partitions两个数据字典,这里不再举例。

 

 

① Global索引hash分区
  Hash分区索引的子句与hash分区表的创建子句完全相同,例如:

  JSSWEB> create index idx_part_hash_id on t_partition_hash(id)

  2 global partition by hash(id)

  3 partitions 3 store in(tbspart01,tbspart02,tbspart03);

  索引已创建。

  查询索引的分区信息也仍是通过user_part_indexes、user_ind_partitions两个数据字典,不再举例。

② 创建Local索引
  在前面学习range分区时,我们已经对Local索引的特性做了非常清晰的概述,因此这里也不再举例,如有疑问,建议再仔细复习range分区的相关示例,如果还有疑问,当面问我好了:)

  综上:

  ? 对于global索引分区而言,在10g中只能支持range分区和hash分区,因此后续示例中不会再提及。

  ? 对于local索引分区而言,其分区形式完全依赖于索引所在表的分区形式,不管从创建语法还是理解难度均无技术含量,因此后续也不再提供示例。

  ? 注意,在创建索引时如果不显式指定global或local,则默认是global。

  ? 注意,在创建global索引时如果不显式指定分区子句,则默认不分区(废话)。

 

 

 

 

3、分区应用:
一般一张表超过2G的大小,ORACLE是推荐使用分区表的,分区一般都需要 创建索引,说到分区索引,就可以分为:全局索引、分区索引,即:global索引和local索引,前者为默认情况下在分区表上创建索引时的索引方式,并 不对索引进行分区(索引也是表结构,索引大了也需要分区,关于索引以后专门写点)而全局索引可修饰为分区索引,但是和local索引有所区别,前者的分区 方式完全按照自定义方式去创建,和表结构完全无关,所以对于分区表的全局索引有以下两幅网上常用的图解:

3.1、对于分区表的不分区索引(这个有点绕,不过就是表分区,但其索引不分区):
 

创建语法(直接创建即可):

CREATE INDEX ON ();

 

 

 

3.2、对于分区表的分区索引:
 

创建语法为:

CREATE INDEX INX_TAB_PARTITION_COL1 ON TABLE_PARTITION(COL1)

  GLOBAL PARTITION BY RANGE(COL1)(

         PARTITION IDX_P1 values less than (1000000),

         PARTITION IDX_P2 values less than (2000000),

         PARTITION IDX_P3 values less than (MAXVALUE)

  );

 

3.3、LOCAL索引结构:
 

 

创建语法为:

 CREATE INDEX INX_TAB_PARTITION_COL1 ON TABLE_PARTITION(COL1) LOCAL;

也可按照分区表的的分区结构给与一一定义,索引的分区将得到重命名。

分区上的位图索引只能为LOCAL索引,不能为GLOBAL全局索引。

 

3.4、对比索引方式:
 

  一般使用LOCAL索引较为方便,而且维护代价较低,并且LOCAL索引是在分区的基础上去创建索引,类似于在一个子表内部去创建索引,这样开销主要是区 分分区上,很规范的管理起来,在OLAP系统中应用很广泛;而相对的GLOBAL索引是全局类型的索引,根据实际情况可以调整分区的类别,而并非按照分区 结构一一定义,相对维护代价较高一些,在OLTP环境用得相对较多,这里所谓OLTP和OLAP也是相对的,不是特殊的项目,没有绝对的划分概念,在应用 过程中依据实际情况而定,来提高整体的运行性能。

 

4、常用视图:
1、查询当前用户下有哪些是分区表:

SELECT * FROM USER_PART_TABLES;

 

2、查询当前用户下有哪些分区索引:

SELECT * FROM USER_PART_INDEXES;

 

3、查询当前用户下分区索引的分区信息:

SELECT * FROM USER_IND_PARTITIONS T

WHERE T.INDEX_NAME=?

 

4、查询当前用户下分区表的分区信息:

SELECT * FROM USER_TAB_PARTITIONS T

WHERE T.TABLE_NAME=?;

 

5、查询某分区下的数据量:

SELECT COUNT(*) FROM TABLE_PARTITION PARTITION(TAB_PARTOTION_01);

 

6、查询索引、表上在那些列上创建了分区:

SELECT * FROM USER_PART_KEY_COLUMNS;

 

7、查询某用户下二级分区的信息(只有创建了二级分区才有数据):

SELECT * FROM USER_TAB_SUBPARTITIONS;

 

5、维护操作:
5.1、删除分区
    ALTER TABLE TABLE_PARTITION DROP PARTITION TAB_PARTOTION_03;

     如果是全局索引,因为全局索引的分区结构和表可以不一致,若不一致的情况下,会导致整个全局索引失效,在删除分区的时候,语句修改为:

     ALTER TABLE TABLE_PARTITION DROP PARTITION TAB_PARTOTION_03 UPDATE GLOBAL INDEXES;

 

5.2、分区合并(从中间删除掉一个分区,或者两个分区需要合并后减少分区数量)
    合并分区和删除中间的RANGE有点像,但是合并分区是不会删除数据的,对于LIST、HASH分区也是和RANGE分区不一样的,其语法为:

ALTER TABLE TABLE_PARTITION MERGE PARTITIONS    TAB_PARTOTION_01,TAB_PARTOTION_02 INTO PARTITION MERGED_PARTITION;

 

 

5.3、分隔分区(一般分区从扩展分区从分隔)
ALTER TABLE TABLE_PARTITION SPLIT PARTITION TAB_PARTOTION_OTHERE AT(2500000)

INTO (PARTITION TAB_PARTOTION_05,PARTITION TAB_PARTOTION_OTHERE);

 

5.4、创建新的分区(分区数据若不能提供范围,则插入时会报错,需要增加分区来扩大范围)
一般有扩展分区的是都是用分隔的方式,若上述创建表时没有创建TAB_PARTOTION_OTHER分区时,在插入数据较大时(按照上述建立规则,超过1800000就应该创建新的分区来存储),就可以创建新的分区,如:

为了试验,我们将扩展分区先删除掉再创建新的分区(因为ORACLE要求,分区的数据不允许重叠,即按照分区字段同样的数据不能同时存储在不同的分区中):

ALTER TABLE TABLE_PARTITION DROP PARTITION TAB_PARTOTION_OTHER;

ALTER TABLE TABLE_PARTITION ADD PARTITION TAB_PARTOTION_06 VALUES LESS THAN(2500000);

 

在分区下创建新的子分区大致如下(RANGE分区,若为LIST或HASH分区,将创建方式修改为对应的方式即可):

ALTER TABLE MODIFY PARTITION ADD SUBPARTITION VALUES LESS THAN(....);

 

5.5、修改分区名称(修改相关的属性信息)
ALTER TABLE TABLE_PARTITION RENAME PARTITION MERGED_PARTITION TO MERGED_PARTITION02;

 

 

5.6、交换分区(快速交换数据,其实是交换段名称指针)
  首先创建一个交换表,和原表结构相同,如果有数据,必须符合所交换对应分区的条件:

  CREATE TABLE TABLE_PARTITION_2

  AS SELECT * FROM TABLE_PARTITION WHERE 1=2;

  然后将第一个分区的数据交换出去:

  ALTER TABLE TABLE_PARTITION EXCHANGE PARTITION TAB_PARTOTION_01

  WITH TABLE TABLE_PARTITION_2 INCLUDING INDEXES;

  此时会发现第一个分区的数据和表TABLE_PARTITION_2做了瞬间交换,比TRUNCATE还要快,因为这个过程没有进行数据转存,只是段名称的修改过程,和实际的数据量没有关系。

 

  如果是子分区也可以与外部的表进行交换,只需要将关键字修改为:SUBPARTITION 即可。

 

5.7、清空分区数据
 

   ALTER TABLE TRUNCATE PARTITION ;

   ALTER TABLE TRUNCATE subpartition ;

 

6、磁盘碎片压缩
   对分区表的某分区进行磁盘压缩,当对分区内部数据进行了大量的UPDATE、DELETE操作后,一定时间需要进行磁盘压缩,否则在查询时,若通过FULL SCAN扫描数据,将会把空块也会扫描到,对表进行磁盘压缩需要进行行迁移操作,所以首先需要操作:

ALTER TABLE ENABLE ROW MOVEMENT ;

 

    对分区表的某分区压缩语法为:

ALTER TABLE

modify partition shrink space;

   对普通表压缩:

ALTER TABLE shrink space;

  对于索引也需要进行压缩,索引也是表:

ALTER INDEX shrink space;
----------------------------------------------------------------------------------------
oracle表空间表分区详解及oracle表分区查询使用方法(转+整理)
此文从以下几个方面来整理关于分区表的概念及操作:

1.表空间及分区表的概念
2.表分区的具体作用
3.表分区的优缺点
4.表分区的几种类型及操作方法
5.对表分区的维护性操作.

表空间及分区表的概念
表空间:是一个或多个数据文件的集合,所有的数据对象都存放在指定的表空间中,但主要存放的是表, 所以称作表空间。
分区表:当表中的数据量不断增大,查询数据的速度就会变慢,应用程序的性能就会下降,这时就应该考虑对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据在物理上存放到多个表空间(物理文件上),这样查询数据时,不至于每次都扫描整张表。

表分区的具体作用
Oracle的表分区功能通过改善可管理性、性能和可用性,从而为各式应用程序带来了极大的好处。通常,分区可以使某些查询以及维护操作的性能大大提高。此外,分区还可以极大简化常见的管理任务,分区是构建千兆字节数据系统或超高可用性系统的关键工具。

分区功能能够将表、索引或索引组织表进一步细分为段,这些数据库对象的段叫做分区。每个分区有自己的名称,还可以选择自己的存储特性。从数据库管理员的角度来看,一个分区后的对象具有多个段,这些段既可进行集体管理,也可单独管理,这就使数据库管理员在管理分区后的对象时有相当大的灵活性。但是,从应用程序的角度来看,分区后的表与非分区表完全相同,使用 SQL DML 命令访问分区后的表时,无需任何修改。

什么时候使用分区表:

表的大小超过2GB。
表中包含历史数据,新的数据被增加都新的分区中。
表分区的优缺点
表分区有以下优点:

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

已经存在的表没有方法可以直接转化为分区表。但是有几种方式可以间接完成这个操作,大家可以参考:oracle分区表的建立方法(包含已经存在的表要分区):http://blog.csdn.net/wanglilin/article/details/7177338
表分区的几种类型及操作方法
范围分区:
范围分区将数据基于范围映射到每一个分区,这个范围是你在创建分区时指定的分区键决定的。这种分区方式是最为常用的,并且分区键经常采用日期。举个例子:你可能会将销售数据按照月份进行分区。
当使用范围分区时,请考虑以下几个规则:

1、每一个分区都必须有一个VALUES LESS THEN子句,它指定了一个不包括在该分区中的上限值。分区键的任何值等于或者大于这个上限值的记录都会被加入到下一个高一些的分区中。
2、所有分区,除了第一个,都会有一个隐式的下限值,这个值就是此分区的前一个分区的上限值。
3、在最高的分区中,MAXVALUE被定义。MAXVALUE代表了一个不确定的值。这个值高于其它分区中的任何分区键的值,也可以理解为高于任何分区中指定的VALUE LESS THEN的值,同时包括空值。

例一:假设有一个CUSTOMER表,表中有数据200000行,我们将此表通过CUSTOMER_ID进行分区,每个分区存储100000行,我们将每个分区保存到单独的表空间中,这样数据文件就可以跨越多个物理磁盘。下面是创建表和分区的代码,如下:

CREATE TABLE CUSTOMER
(
CUSTOMER_ID NUMBER NOT NULL PRIMARY KEY,
FIRST_NAME VARCHAR2(30) NOT NULL,
LAST_NAME VARCHAR2(30) NOT NULL,
PHONEVARCHAR2(15) NOT NULL,
EMAILVARCHAR2(80),
STATUS CHAR(1)
)
PARTITION BY RANGE (CUSTOMER_ID)
(
PARTITION CUS_PART1 VALUES LESS THAN (100000) TABLESPACE CUS_TS01,
PARTITION CUS_PART2 VALUES LESS THAN (200000) TABLESPACE CUS_TS02
)
例二:按时间划分

CREATE TABLE ORDER_ACTIVITIES
(
ORDER_ID NUMBER(7) NOT NULL,
ORDER_DATE DATE,
TOTAL_AMOUNT NUMBER,
CUSTOTMER_ID NUMBER(7),
PAID CHAR(1)
)
PARTITION BY RANGE (ORDER_DATE)
(
PARTITION ORD_ACT_PART01 VALUES LESS THAN (TO_DATE('01- MAY -2003','DD-MON-YYYY')) TABLESPACEORD_TS01,
PARTITION ORD_ACT_PART02 VALUES LESS THAN (TO_DATE('01-JUN-2003','DD-MON-YYYY')) TABLESPACE ORD_TS02,
PARTITION ORD_ACT_PART02 VALUES LESS THAN (TO_DATE('01-JUL-2003','DD-MON-YYYY')) TABLESPACE ORD_TS03
)
例三:MAXVALUE

CREATE TABLE RangeTable
(
idd INT PRIMARY KEY ,
iNAME VARCHAR(10),
grade INT
)
PARTITION BY RANGE (grade)
(
PARTITION part1 VALUES LESS THEN (1000) TABLESPACE Part1_tb,
PARTITION part2 VALUES LESS THEN (MAXVALUE) TABLESPACE Part2_tb
);
列表分区:
该分区的特点是某列的值只有几个,基于这样的特点我们可以采用列表分区。

例一

CREATE TABLE PROBLEM_TICKETS
(
PROBLEM_ID NUMBER(7) NOT NULL PRIMARY KEY,
DESCRIPTION VARCHAR2(2000),
CUSTOMER_ID NUMBER(7) NOT NULL,
DATE_ENTERED DATE NOT NULL,
STATUS VARCHAR2(20)
)
PARTITION BY LIST (STATUS)
(
PARTITION PROB_ACTIVE VALUES ('ACTIVE') TABLESPACE PROB_TS01,
PARTITION PROB_INACTIVE VALUES ('INACTIVE') TABLESPACE PROB_TS02
)
例二

CREATE TABLE ListTable
(
id INT PRIMARY KEY ,
name VARCHAR (20),
area VARCHAR (10)
)
PARTITION BY LIST (area)
(
PARTITION part1 VALUES ('guangdong','beijing') TABLESPACE Part1_tb,
PARTITION part2 VALUES ('shanghai','nanjing') TABLESPACE Part2_tb
);
)
散列分区:
这类分区是在列值上使用散列算法,以确定将行放入哪个分区中。当列的值没有合适的条件时,建议使用散列分区。
散列分区为通过指定分区编号来均匀分布数据的一种分区类型,因为通过在I/O设备上进行散列分区,使得这些分区大小一致。
例一:

CREATE TABLE HASH_TABLE
(
COL NUMBER(8),
INF VARCHAR2(100)
)
PARTITION BY HASH (COL)
(
PARTITION PART01 TABLESPACE HASH_TS01,
PARTITION PART02 TABLESPACE HASH_TS02,
PARTITION PART03 TABLESPACE HASH_TS03
)
简写:

CREATE TABLE emp
(
empno NUMBER (4),
ename VARCHAR2 (30),
sal NUMBER
)
PARTITION BY HASH (empno) PARTITIONS 8
STORE IN (emp1,emp2,emp3,emp4,emp5,emp6,emp7,emp8);
hash分区最主要的机制是根据hash算法来计算具体某条纪录应该插入到哪个分区中,hash算法中最重要的是hash函数,Oracle中如果你要使用hash分区,只需指定分区的数量即可。建议分区的数量采用2的n次方,这样可以使得各个分区间数据分布更加均匀。

组合范围散列分区
这种分区是基于范围分区和列表分区,表首先按某列进行范围分区,然后再按某列进行列表分区,分区之中的分区被称为子分区。

CREATE TABLE SALES
(
PRODUCT_ID VARCHAR2(5),
SALES_DATE DATE,
SALES_COST NUMBER(10),
STATUS VARCHAR2(20)
)
PARTITION BY RANGE(SALES_DATE) SUBPARTITION BY LIST (STATUS)
(
PARTITION P1 VALUES LESS THAN(TO_DATE('2003-01-01','YYYY-MM-DD'))TABLESPACE rptfact2009
(
SUBPARTITION P1SUB1 VALUES ('ACTIVE') TABLESPACE rptfact2009,
SUBPARTITION P1SUB2 VALUES ('INACTIVE') TABLESPACE rptfact2009
),
PARTITION P2 VALUES LESS THAN (TO_DATE('2003-03-01','YYYY-MM-DD')) TABLESPACE rptfact2009
(
SUBPARTITION P2SUB1 VALUES ('ACTIVE') TABLESPACE rptfact2009,
SUBPARTITION P2SUB2 VALUES ('INACTIVE') TABLESPACE rptfact2009
)
)
复合范围散列分区:
这种分区是基于范围分区和散列分区,表首先按某列进行范围分区,然后再按某列进行散列分区。

create table dinya_test
(
transaction_id number primary key,
item_id number(8) not null,
item_description varchar2(300),
transaction_date date
)
partition by range(transaction_date)subpartition by hash(transaction_id) subpartitions 3 store in (dinya_space01,dinya_space02,dinya_space03)
(
partition part_01 values less than(to_date(‘2006-01-01','yyyy-mm-dd')),
partition part_02 values less than(to_date(‘2010-01-01','yyyy-mm-dd')),
partition part_03 values less than(maxvalue)
);
有关表分区的一些维护性操作:
添加分区
以下代码给SALES表添加了一个P3分区

ALTER TABLE SALES ADD PARTITION P3 VALUES LESS THAN(TO_DATE('2003-06-01','YYYY-MM-DD'));
注意:以上添加的分区界限应该高于最后一个分区界限。

以下代码给SALES表的P3分区添加了一个P3SUB1子分区

ALTER TABLE SALES MODIFY PARTITION P3 ADD SUBPARTITION P3SUB1 VALUES('COMPLETE');
删除分区
以下代码删除了P3表分区:

ALTER TABLE SALES DROP PARTITION P3;
在以下代码删除了P4SUB1子分区:

ALTER TABLE SALES DROP SUBPARTITION P4SUB1;
注意:如果删除的分区是表中唯一的分区,那么此分区将不能被删除,要想删除此分区,必须删除表。

截断分区
截断某个分区是指删除某个分区中的数据,并不会删除分区,也不会删除其它分区中的数据。当表中即使只有一个分区时,也可以截断该分区。通过以下代码截断分区:

ALTER TABLE SALES TRUNCATE PARTITION P2;
通过以下代码截断子分区:

ALTER TABLE SALES TRUNCATE SUBPARTITION P2SUB2;
合并分区
合并分区是将相邻的分区合并成一个分区,结果分区将采用较高分区的界限,值得注意的是,不能将分区合并到界限较低的分区。以下代码实现了P1 P2分区的合并:

ALTER TABLE SALES MERGE PARTITIONS P1,P2 INTO PARTITION P2;
拆分分区
拆分分区将一个分区拆分两个新分区,拆分后原来分区不再存在。注意不能对HASH类型的分区进行拆分。

ALTER TABLE SALES SBLIT PARTITION P2 AT(TO_DATE('2003-02-01','YYYY-MM-DD')) INTO (PARTITION P21,PARTITION P22);
接合分区(coalesca)
结合分区是将散列分区中的数据接合到其它分区中,当散列分区中的数据比较大时,可以增加散列分区,然后进行接合,值得注意的是,接合分区只能用于散列分区中。通过以下代码进行接合分区:

ALTER TABLE SALES COALESCA PARTITION;
重命名表分区
以下代码将P21更改为P2

ALTER TABLE SALES RENAME PARTITION P21 TO P2;
相关查询
-- 跨分区查询
select sum( ) from
(select count() cn from t_table_SS PARTITION (P200709_1)
union all
select count(*) cn from t_table_SS PARTITION (P200709_2)
);

--查询表上有多少分区
SELECT * FROM useR_TAB_PARTITIONS WHERE TABLE_NAME='tableName'

--查询索引信息
select object_name,object_type,tablespace_name,sum(value)
from v$segment_statistics
where statistic_name IN ('physical reads','physical write','logical reads')and object_type='INDEX'
group by object_name,object_type,tablespace_name
order by 4 desc

--显示数据库所有分区表的信息:
select * from DBA_PART_TABLES

--显示当前用户可访问的所有分区表信息:
select * from ALL_PART_TABLES

--显示当前用户所有分区表的信息:
select * from USER_PART_TABLES

--显示表分区信息 显示数据库所有分区表的详细分区信息:
select * from DBA_TAB_PARTITIONS

--显示当前用户可访问的所有分区表的详细分区信息:
select * from ALL_TAB_PARTITIONS

--显示当前用户所有分区表的详细分区信息:
select * from USER_TAB_PARTITIONS

--显示子分区信息 显示数据库所有组合分区表的子分区信息:
select * from DBA_TAB_SUBPARTITIONS

--显示当前用户可访问的所有组合分区表的子分区信息:
select * from ALL_TAB_SUBPARTITIONS

--显示当前用户所有组合分区表的子分区信息:
select * from USER_TAB_SUBPARTITIONS

--显示分区列 显示数据库所有分区表的分区列信息:
select * from DBA_PART_KEY_COLUMNS

--显示当前用户可访问的所有分区表的分区列信息:
select * from ALL_PART_KEY_COLUMNS

--显示当前用户所有分区表的分区列信息:
select * from USER_PART_KEY_COLUMNS

--显示子分区列 显示数据库所有分区表的子分区列信息:
select * from DBA_SUBPART_KEY_COLUMNS

--显示当前用户可访问的所有分区表的子分区列信息:
select * from ALL_SUBPART_KEY_COLUMNS

--显示当前用户所有分区表的子分区列信息:
select * from USER_SUBPART_KEY_COLUMNS

--怎样查询出oracle数据库中所有的的分区表
select * from user_tables a where a.partitioned='YES'

--删除一个表的数据是
truncate table table_name;

--删除分区表一个分区的数据是
alter table table_name truncate partition p5;
--------------------------------------------------------------------------------------

猜你喜欢

转载自blog.csdn.net/Leon_Jinhai_Sun/article/details/86410959
今日推荐