数据库知识(oracle)
1. 查询需要注意的一些事项
关系表,笛卡尔积
尽量不使用select *,而使用select 字段列表
-
尽量不使用not in
如a,b表同结构,数据量很大,则可使用以下语句代替 select * from a where a.c not in (select c from b ) a) select a.* from a, b where a.c = b.c(+) and b.c is null b) select * from a where not exist(select a.* from a,b where a.c=b.c)
-
尽量在应用代码中使用绑定变量进行查询减少
如用EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM TEST WHERE NAME=:A' INTO V_COUNT USING V_VAR 替换EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM TEST WHERE NAME=''ABC''' INTO V_COUNT;
-
用UNION-ALL 替换UNION
当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序. 如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高.
-
用Where子句替换HAVING子句
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销
-
用EXISTS替代IN
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率
-
用EXISTS替换DISTINCT
当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换
尽量不使用select ..for update
2. 使用索引的几个注意点
访问Table的方式
a. 全表扫描
全表扫描就是顺序地访问表中每条记录. ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描.
b. 通过ROWID访问表
你可以采用基于ROWID的访问方式情况,提高访问表的效率, , ROWID包含了表中记录的物理位置信息.ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系. 通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高.
索引的介绍
索引是一种树状的结构,基本所有的数据库都支持索引,用来提高数据的检索速度。
索引需要另外的存储空间,而且需要多次的io操作,同时使用索引会给插入,删除和更新等操作带来负面影响,因此在使用索引时要注意衡量,不是索引越多越好!
索引的结构
索引由索引叶节点,索引分支节点组成,索引项就存储在索引叶节点的数据块当中。而每一个索引叶节点都只能容纳一定的索引项,当数据量超过数据块的容量后,就会导致分支节点的生成和叶节点的增加,这会迫使进行索引结构的重新组织,导致索引层高度的增加,新划分的数据块使用率也会很低,造成磁盘利用率下降,最终影响了性能。
索引的更新
先将最初的叶子节点中的索引项标记为死亡,然后再在恰当的叶子节点中插入新的索引项,当然如果新的插入需要空间仍会导致数据块分割。删除的数据对应的索引项会被标记为死亡,因而成为废弃的索引项,随着增删改操作的不断进行,废弃的叶子节点越来越多,索引分支节点也越来越多,索引层次也随着加高,使用索引访问的效率将会剧烈降低甚至最后比不使用索引还低效。
使用索引的原则
- 索引应建立在经常使用where语句的列上或经常需要排序的字段
- 如果有多表连接,则应该在连接列上建立索引以提高检索速度
- 索引应该建立在大表上,小表上建立索引没必要
- 应该选择字段值比较稀疏的字段作为索引列
- 使用索引会带来很多的负面影响,仔细衡量查询与修改的比例限制索引的个数
- 索引的存放表空间应尽量与数据表错开
- 通过建立复合索引来替代多个单列索引
- 注意建立索引的方式,使null查询也走索引
在oracle中,null是不会放在索引中的,这个在null值比较少的情况下是没办法用到索引,这里主要介绍一下怎么样能让null使用到索引.
- 可以bai把那些null值设置成一个特殊的值,比du如"N/A"
- where nvl(col,0) =0
在col字段上zhi创建一个函数索引
create index ind_col on table(nvl(col,0));
CREATE INDEX TEST.IND_T01_OBJECT_NAME ON TEST.T01(OBJECT_NAME) TABLESPACE TBS_INDEX;
CREATE INDEX TEST.IND_T01_OBJECT_NAME ON TEST.T01(OBJECT_NAME,1) TABLESPACE TBS_INDEX;
建立复合索引
CREATE INDEX TEST.IND_T01_OBJECT_NAME ON TEST.T01(OBJECT_NAME) TABLESPACE TBS_BIG;
CREATE INDEX TEST.IND_T01_OBJECT_ID ON TEST.T01(OBJECT_ID) TABLESPACE TBS_BIG;
-- 复合索引
CREATE INDEX TEST.IND_T01_COMP ON TEST.T01(OBJECT_NAME,OBJECT_ID) TABLESPACE TBS_BIG;
不走索引的几种情况
-
走索引的开销要比不走索引的大
- 小表,全表扫面的效率比通过索引扫描的高
- 数据集占全表比例超过一定值,导致全表扫描比走索引高效
- 索引字段值分布比较均衡,如性别
-
索引的使用不当
-
使用索引的方式与创建索引的方式有出入
比如创建普通字段索引,但查询时该字段加了函数如nvl()、trim()等
-
创建了复合索引,前导列使用不当
比如在(col1,col2,col3)创建了索引,使用时以col2或col3为前导列
-
避免在索引列上使用计算
SELECT * FROM DEPT WHERE SAL * 12 > 250000
-
-
表的统计信息过时
一般优化器使用基于成本的方式进行数据扫描,而该方式需要使用到表的统计信息,即统计信息越新越接近表的真实情况,则优化器的解析计划也就越优,进而能进行最高效的查询。如果统计信息过时或无法代表当前表的具体情况,则可能发生有索引却不走索引的情况
3. 表空间与数据文件的关系
表空间
1.是一种数据库逻辑概念
是用户可以在数据库中使用的最大的逻辑存储单元
用户在数据库中建立的所有内容都会存储在表空间中
可以通过dba_tablespaces或user_tablespaces来查看表空间的情况
2.用户和表空间
一个用户对应一个默认表空间和一个临时表空间
一个用户可以使用多个表空间
所有对数据库对象的操作都会在默认表空间或者临时表空间进行
数据文件
- 是oracle存放数据的物理位置,一个表空间至少有一个数据文件,通常是有多个数据文件。
- Oracle通过以下层次组织文件和数据:
- 数据库是由一个或者多个表空间组成
- 表空间由一个或者多个数据文件组成,表空间中包含段
- 段(表,索引等)由一个或者多个盘区组成,段存储在表空间中。
- 盘区是在磁盘上的连续的块的组。一个盘区在一个表空间中,且仅在表空间中单一的文件中。
- 块是数据库中最小的分配单元,是数据库使用的最小的io单元
- 可通过Dba_data_files和dba_temp_files查询到对应的数据文件和临时文件信息
两者的联系
- 表空间是逻辑概念,数据文件是物理概念,表空间是基于数据文件而存在的。逻辑概念要比物理概念更接近于数据库使用者,即逻辑概念是从oracle方便用户使用的角度而言的。
- 逻辑空间包括表空间、段、盘区、数据块,oracle通过这些逻辑空间管理数据的存储。
- 数据文件从物理角度讲是基于操作系统的块上的。
- 表空间和物理文件的对应关系是1对1或者1对多。
- 数据文件对于表空间而言只能是独占的。
查询
SELECT TABLESPACE_NAME,CONTENTS,BIGFILE FROM DBA_TABLESPACES;
SELECT TABLESPACE_NAME,FILE_NAME,BYTES/1024/1024 SIZE_MB FROM DBA_TEMP_FILES
UNION
SELECT TABLESPACE_NAME,FILE_NAME,BYTES/1024/1024 SIZE_MB FROM DBA_DATA_FILES
4. 数据的删除及注意事项
-
删除数据前最好先备份
可通过create table …as select …语句备份或者使用工具导出需要删除的表或者记录集。
-
检查删除语句的条件是否足够严格
确保where中的所有限制条件完全能够确定需要删除的记录而不会多删除,可通过先使用具有相同where条件的select语句验证,确定无误后在使用delete语句删除数据。
删除数据应选准时机,实施尽量不要在业务繁忙的时间段删数据以免锁表等待或者影响系统使用。研发环境应事先告知所有人员。
选择合适的删除方式删除数据
重建索引或者分析表统计信息
删除数据的方式
-
全表删除
确定需要整张表的数据都删除后,可选择使用delete和truncate前者为dml语句,会产生log,受数据量影响会比较慢,后者为ddl语句,不产生log,速度快;误删后前者可通过闪回等手段恢复,后者只能从数据备份恢复。
-
部分数据删除
小部分数据的删除可以直接在前面提到的原则上对数据进行删除;由于删除数据需要生成回滚日志,写入日志文件,可能还会涉及日志的归档,效率比较低,当需要删除大部分数据时,索引结构也会 产生比较大的改变,比如说需要删除一张有1亿条数据的表中的8000 万条数据,则使用常规的删除语句将会是一个很漫长的等待和煎熬, 为提高删除效率,可先备份需要保留的数据到新建的表中,然后全表删除数据,最后回写保留的数据和重建索引、删除保留数据备份
5. 注意使用select for update
方便性
修改数据、导入数据等操作
危险性
锁表影响正常使用,如用户表
原则
- 尽量缩小加锁集,如select …where …for update
- 及时commit
- 尽量少使用
Java基础
多态
概念
多态是同一个行为具有多个不同表现形式或形态的能力
多态就是同一个接口,使用不同的实例而执行不同操作
多态的特点:
把子类对象赋给父类变量
在运行时期会表现出具体的子类特征调用子类具体的方法。
多态的特点
把子类对象赋给父类变量
在运行时期会表现出具体的子类特征调用子类的.
多态作用
- 当把不同的子类对象都当作父类类型来看待,可以屏蔽不同子类对象之间的实现差异,
- 从而写出通用的代码达到通用编程,以适应需求的不断变化。
- 在这里使用了多态后,只需要写一个方法就能达到相同的功能。
封装
概念
把对象的状态和行为看成一个统一的整体 , 将字段和方法放到一个类中
-
信息隐藏: 把不需要让外界知道的信息隐藏起来
尽可能隐藏对象功能实现细节,向外暴露方法,保证外界安全访问功能;
好处
- 保证数据的安全
- 提高组件的复用性
继承
概念
从已有类中,派生出新的类,新的类中吸收已有类当中的状态和行为,并能扩展出新的能力。
Java继承是使用已有类作为基础,建立新的类。
如何表示继承
extends
继承的作用
- 解决代码重复问题
- 真正的作用,表示出一个体系
先写父类还是子类
多个类中存在共同的代码,此时可以抽取出一个父类
子类与父类的继承关系
子类继承父类之后,可以拥有父类的某一些状态和行为。
子类复用了父类的功能或状态.
但并不是父类当中所有的内容,子类都可以直接使用。
子类可以使用父类当中的哪些成员
- 如果父类中的成员使用public修饰,子类继承.
- 如果父类中的成员使用protected修饰,子类也继承,不同包也能继承
- 如果父类和子类在同一个包中,此时子类可有继承父类中默认的成员. 不同包不能继承默认成员
- 如果父类中的成员使用private****修饰,子类打死都继承不到.因为private只能在本类中访问.
- 父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同
方法的覆盖
概念
子类拓展了父类,可以获得父类的部分方法和成员变量。可是当父类的某个方法不适合于子类本身的特征时,可以进行覆盖重新定义父类当中的方法
方法覆盖的原则
- 实例方法签名必须相同(方法名+方法参数)
- 子类方法的返回值类型是和父类方法的返回类型相同
- 子类方法的访问权限比父类方法访问权 限更大或相等
判断方法是否是覆盖方法
注解@Override
方法有覆盖的概念,字段没有
重载和方法覆盖的区别
重载:Overload
重写:Override
方法重载
作用:
解决了同一个类中,相同功能的方法名不同的问题.
既然是相同的功能,那么方法的名字就应该相同
规则:同类中,方法名相同,方法参数列表不同
重写
作用:
解决子类继承父类之后,可能父类的某一个方法不满足子类的具体特征,此时需要重新在子类中定义该方法,并重写方法体
规则:父类和子类的方法签名是相同的
字符串
String创建方式
其实它是一个char[]数组
分类
不可变字符串:String
可变字符串:StringBuilder/StringBuffer
字符串为空的两种形式
-
表示引用为空: String str = null;
还没有初始化,
-
表示空字符串: String str = “”;
已经创建了对象,已经分配了内存
内存当中的内容为空。
不可变类型通常有如下特征
- 所有成员变量被private修饰
- 没有写或修改成员变量的方法如 setxxx,只提供构造函数,一次生成
- 不会被子类覆盖,通过类定义为final或把类方法定义为final
- 如果类成员不是不可变量,那么在成员初始化或使用get方法获取成员变量需要通过clone方法,确保类的不可变
实现字符串拼接
StringBuffer:线程安全
StringBuilder:非线程安全
值传递与引用传递
现象:String作为参数时候,对形参值的修改不会影响到实参,对于StringBuffer参数类型,修改影响到了。
原因:
由于String参数是按值传递的(其他基本类型也一样),此时会创建一个i的副本,该副本和i有相同的值。
而stringbuffer由于是一个类,因此按引用传递,即对象的地址空间,修改的是这个地址指向的字符串内容
判断一个字符串是否为空
- == 比较两个内存地址是否相等
- 使用equals ,由Object中继承过来, 在object中和 == 相同,即通过比较内存地址但是String 类当中覆盖了equals方法
equals源码解析
- 判断传入的值是否与当前字符串地址相同,如果相同就直接返回true
- 判断传入的字符串是否为String类型如果不,直接返回false
- 获取传入字符的长度是否与当前字符串长度相等如果相等 ,拿出当前字符串的char数组 与传入字符串的char数组通过循环逐个进行对比 如果全部相等返回true
字符串内存分析
String str1 = "ABCD"
先在常量池中找,找不到在常量池中创建,有就直接用,不会创建新的地址,如果常量池当中没有的话,就会在常量池中创建一个对象
String str2 = new String("ABCD")
至少创建一个对象,使用了new,在堆中创建至少得要创建一个对象,看一下常量池中,有没有传入的字符串常量,如果说没有的话,会创建一个字符串常量,放到常量池中
字符串编译优化
把char数组转字符串
new String(char);
把字符串转为字符数组
str.toCharArray();
获取字符串当中指定位置的字符
str.charAt(2)
indexOf
:返回字符串在父字符串第一次出现的位置
lastIndexOf
:最后出现的位置
变为大写:toUpperCase()
小写:toLowerCase()
忽略大小写比较字符串是否相等
equalsIgnoreCase()
splist
:把字符串分割为数组
startsWith()
:是否以指定的字符串开头
substring(1)
:从指定位置截取,一直到结束
substring(1,4)
:指定位置开始,到指定位置结束
把一个单词的首字母变成大写
trim()
:去除字符串当中左右两端的空格
replace("q","g")
:使用一个新字符串替换老字符串,q换成g
replace(" ","")
去除字符串中所有的空格
集合
ArrayList集合
特点:
- ArrayList是最常见的List,他和数组有同样的特点
- 随机访问(相对于顺序访问)效率高
- 读快写慢,由于写的过程中需要涉及元素的移动,因此写操作的效率比较低
主要成员变量:
transient Object[] elementData:list的数据域,elementdata是一个缓存数组,为了性能的考虑预留了一些容量,当容量不足时候会扩充容量,因此可能会存在大量的空间没有实际存储元素
int size:当前list的长度,即实际存放数据的长度
int modCount:继承自AbstractList,记录ArrayList结构性变化的次数,在涉及结构变化都会增加modCount值(如新增、删除)
随机访问
在一组长度为n的数据中,要找到其中第i个元素,只需要通过下标既可以找到
顺序访问
在一组长度为n的数据中,要找到第i个元素,只能重头到尾或者从尾到头便利,直到找到第i个元素,需要依次找i或者n-i次
读取元素:
数组在内存中占用连续的存储空间,给定一段数据,如果知道他的起始位置和数据长度,就能很快在内存中定位他。
例如一个int型数据,在java中长度是4个字节,假设初始位置是0x00000001,则内存地址如下
规律总结:
1)所有数据块的内存是连续的,
2)所有数据库长度是相同的
由此 第i个元素的地址=起始地址+单位长度*i
另外,Object对象的长度如何固定?Object存放的是对象的引用,长度也是固定的
添加元素:
1)检查elementdata的长度,如果添加后的大小超过了elementdata的长度,需要进行容量修正。通过使用新容量初始化一个新的数组,将原有elementData中的元素等位复制过去
2)指定index添加元素:把对应的值,通过前一个值覆盖后一个值的方式,依次往后移动,把要插入的值覆盖到指定的位置
删除元素:
把要删除位置后一个值,复制覆盖到要删除的位置,后续依次进行复制覆盖,把最后一个值变为null
什么是并发修改异常
在迭代集合的过程当中,是不允许直接修改集合结构的
比如添加,删除元素(个数的改变)
如果改变了,就会造成并发修改异常
并发修改异常原因
在获取迭代器时, 会和集合进行关联保持两边数据一至
内部会有一个modCount 和 expectedModCount默认他们两个是相等的
modCount:集合记录修改次数
expectedModCount迭代器当中记录集合修改的次数
在我们取元素的时候,都会先做一个判断
modCount 和 expectedModCount两个默认是相等的
会判断modCount与expectedModCount是否相等,如果不相等就不会抛出一个并发修改异常
解决并发修改异常
其他:
LinkedList(链表):顺序访问、读慢写快
Vector:矢量队列,和ArrayList不同,Vector是线程安全
Stack:是Vector子类,提供了一些与栈特性相关的方法
HashMap
特点:
- Map是一种由多组Key-value集合在一起的结构,其中key是不能重复
- Key是唯一、无序的,可为空
- 使用数组存放这些键值对,键值对于数组下标的对应关系由key值的hashcode来决定
主要成员变量(1.7之前):
transient Node<K,V>[] table:存储数据的核心成员变量,HashMap的核心成员变量,该数组用于记录HashMap的所有数据,他的每一个下标都对应一条链表。换言之,所有hash冲突的数据,都会被存放到同一条链表中。Entry则是该链表的结点元素
int size:实际键值对数
loadFactor:加载因子,用于决定table的扩容量
HashCode和equals关系
- hashcode一致,同时equals返回false的对象被正确写入HashMap。
- hashcode不一致,同时equals返回true的对象被正确写入HashMap。
- hashcode一致,同时equals返回true的对象发生了hash冲突,第二个HS对象代替了第一个。
当且仅当hashCode一直,且equals比对一致的对象,才会被hashMap认为是同一个对象
示例
1)例如Key 1的hashcode经过计算得到51,那他被作为键值存入hashMap后,table[51]对应的entry,key值就是1。
2)当存入另一个对象Key的hashcode也是51,他会在存入链表里,和之前的字符串同时存在。
put流程
- 计算键值key的hash值。
- 根据hash值和table长度来确定下标。
- 存入数组。
- 根据key值和hash值对比,确定是创建链表节点还是替换之前的链表值。
- 根据增加后的size来扩容,确定下一个扩容阀值,确定是否需要使用替代hash算法。
get流程
当需要查找指定对象的时候,会先找到hashcode对应的下标,
然后遍历链表,调用对象的equals方法进行比对从而找到对应的对象。
代码的整洁
混乱代码的代价
- 混乱的代码到后期往往会形成一团乱麻,随着代码混乱程度的增加,代码将无法维护
- 混乱代码也将导致团队的生产力逐渐趋近于0
谨慎命名
- 名副其实
- 避免误导
- 去掉冗余
- 使用读的出来的名称
- 谨慎,不要俏皮
函数和类
- 保持函数短小精悍,一个函数只做一件事
- 保持类短小精悍,单一权责原则
- 每个函数一个抽象层级
- 使用多态替换switch
- 减少函数参数
- 重构
坏注释和好注释
- 去掉无意义的注释
- 注释掉的代码
- 我们是作者——为每个类添加头注释
- 警示
- TODO注释
- 让代码自解释
良好的格式
- 自顶向下的阅读顺序
- 缩进与间隔
错误处理
- 抽离 try / catch 代码块
- 使用异常代替错误码
- 别返回 null 值、别传递 null 值
测试代码
- 保持测试整洁