大家好,今天小编来为大家解答ORACLEMINUS(oracle minus报错表不存在)这个问题,很多人还不知道,现在让我们一起来看看吧!
2.将连接,资源,DBA授予用户名; //授权授予
连接,资源,dba,sysdba到用户名;
3.连接用户名/密码//输入。
.jpg)
4.select table_name,column_name来自user_tab_columns
table_name=\’mview_log \’; //在表中查询表名称,字段名称等。
5。如何执行脚本SQL文件? sql@path/filename.sql;
6。Oracle OLEDB提供商在命令中执行多个SQL语句,并且SQL Server之间有很小的区别
更不用说,SQL Server只需要使用\“; \”来拆分多个SQL语句,而Oracle需要遵守Oracle调用规范。
也就是说,除了半分割外,声明主体还必须被开始/结束包围;
使用C的描述应如下:
this.oledbCommand1.commandText=\”开始插入group_info
(group_id,group_name)值(1,\\\'2 \\\');插入
group_info(group_id,group_name)值(2,\\\’2 \\\’);结尾;\”;
7.查询用户下方的所有表从USER_TAB_COLUMNS中选择不同的table_name;
8。如何搜索第一个N记录?从(从CardKind Order中选择* *
cardkind)
9。在用户下找到所有表:从选项卡中选择* *;
2。显示当前连接的用户
SQLShow用户
3。检查系统具有哪些用户
sqlSelect *来自all_users;
4。创建新用户并授权
sqlcreate用户a;(默认情况下内置在系统表空间中)
sqlgrant Connect,资源到a;
5。连接到新用户
sqlconn a/a
6。查询当前用户下的所有对象
sqlSelect *从tab;
7。创建第一个表
sqlcreate表A(数字);
8。查询表结构
sqldesc a
9。插入新记录
sqlinsert成值(1);
10。查询记录
sqlSelect *来自a;
11。更改记录
sqlupdate a集a=2;
12。删除记录
来自a的sqldelete;
13。回头
sqlroll;
sqlrollback;
14。提交
sqlcommit;
从中选择*
(选择t。
其中等级=2;
46。如何将输入添加到字符串中?
select \’欢迎访问\’|| chr(10)|| \’www.csdn.net \’
47。中文如何分类?
在Oracle9i之前,用二进制编码对中文进行分类。
在Oracle9i中,已经添加了Pinyin,“自由基”和“中风”分类的新功能。设置NLS_SORT值
schinese_radical_m从根本(一阶),中风(二阶)排序
schinese_stroke_m按中风(一阶),激进分子(二阶)排序
schinese_pinyin_m pinyin排序
48。在oracle8i中可以使用中文中的对象名称吗?
能
49。如何更改胜利中的SQL*Plus启动选项?
可以在$ oracle_home/sqlplus/admin/glogin.sql中设置SQL*Plus自己的选项设置
设置。
50。如何修改Oracel数据库的默认日期?
Alter Session设置NLS_DATE_FORMAT=\’yyymmddhh24miss \’;
或者
您可以在init.ora中添加一行
nls_date_format=\’yyyymmddhh24miss \’
51。如何将小桌子放入保留池中?
Alter表XXX存储(Buffer_Pool Keep);
52。如何检查是否安装了补丁?
检查该OrainVentory
53。如何使Select语句在查询结果中自动生成序列编号?
从桌子中选择Rownum,Col;
54。如何知道数据库中的表所在的表空间?
从user_tables中选择tableSpace_name where table_name=\’test \’;
从User_tables选择*有一个字段tablespace_name,(oracle);
从dba_segments中选择*;
55。如何像原始桌子一样快速制作备份表?
创建表new_table为(select * from old_table);
55。如何在sqlplus下修改程序?
从user_source选择行,trim(text)t,name=\’a \’按行订单;
56。如何意外撤消程序?
更改系统杀死会话,杀死该会话,但是您必须先找出她的会话ID
或者
只需将过程更改为一个新名称即可。
57。什么是SQL参考?
它是SQL用户手册,包括语法,函数等。已下载了Oracle网站的文档中心。
58。如何查看数据库的状态?
Unix
PS -EF | Grep ora
视窗
查看服务是否已启动
我可以连接到数据库吗
59。如何修改表的主要键?
Alter Table AAA
下降约束aaa_key;
Alter Table AAA
添加约束AAA_KEY主键(A1,B1);
60。更改数据文件的大小?
使用Alter数据库…。 DataFile…。
原始数据文件将损害数据文件的大小。
61。如何检查在Oracle中运行的哪些程序?
查看V $ Sessions表
62。我如何看到数据库中有多少个表空间?
从dba_tablespaces中选择* *;
63。如何修改与Oracle数据库的用户连接数量?
修改initsid.ora,增加过程并重新启动数据库。
64。如何找出记录的最后更新时间?
您可以使用Logminer查看
65。如何在PL/SQL中读写文件?
UTL_FILE软件包允许用户通过PL/SQL读取和编写操作系统文件。
66。如何将\“ \”放入记录中?
插入一个值(translate(\’at {} t \’,\’at {} \’,\’at \’));
67。如何将查询参数添加到EXP?
EXP用户/通过文件=A.DMP表(BSEMPMS)
query=\’\”其中emp_no=\\’s09394 \\’\\”;
68。关于Oracle8i支持简化和传统中文的角色集问题?
ZHS16GBK可以支持
69。数据保护是什么软件?
这是备用的替代产品
70。如何创建SPFILE?
sqlconnect/as sysdba
sqlSelect *来自V $版本的SQLSelect *;
来自Spfile的sqlcreate pfile;
来自pfile=\’e: \\ ora9i \\ admin \\ eygle \\ pfile \\ pfile \\ init.ora \’;
该文件已创建。
sqlcreate spfile=\’e: \\ ora9i \\ database \\ spfileeygle.ora \’来自
pfile=\’e: \\ ora9i \\ admin \\ eygle \\ pfile \\ init.ora \’;
该文件已创建。
71。内核参数的应用?
shmmax
含义:此设置无法确定Oracle数据库或操作系统使用多少物理内存,它仅确定
决定
可用的最大内存量。此设置不会影响操作系统的内核资源。
设置方法:0.5*物理记忆
示例:设置SHMSYS:SHMINFO_SHMMAX=10485760
shmmin
含义:共享内存的最小尺寸。
设置方法:通常,将其设置为1。
示例:设置SHMSYS:SHMINFO_SHMMIN=1:
Shmmni
含义:系统享受的最大内存段数量。
示例:设置SHMSYS:SHMINFO_SHMMNI=100
shmseg
含义:每个用户进程可以使用的共享内存段数量。
示例:设置SHMSYS:SHMINFO_SHMSEG=20:
Semmni
含义:系统中信号量标识符的最大数量。
设置方法:将此变量的值设置为此系统上所有Oracle实例的最int.ora
大的
过程的价值为10。
示例:设置SEMSYS:SEMINFO_SEMMNI=100
semmns
含义:系统中的最大寄生虫数量。
设置方法:可以通过以下方法计算此值:在每个Oracle实例的Initsid.ora中
的
过程值的总和(不包括最大过程参数) +最大过程2 + 10
Oracle实例数。
示例:设置SEMSYS:SEMINFO_SEMMNS=200
SEMMSL:
含义:集合中的最大信号量数。
设置方法:设置Initsid.ora中最大进程的值,为10 +所有Oracle实例。
示例:设置SEMSYS:SEMINFO_SEMMSL=-200
72。如何检查哪些用户具有SYSDBA和SYSOPER权限?
sqlconn sys/change_on_install
sqlSelect *来自v_ $ pwfile_users;
73。如何单独备份一个或多个表?
EXP用户/密码表=(表1,,表2)
74。如何单独备份一个或多个用户?
EXP System/Manager所有者=(用户1,用户2,,用户n)file=导出文件
75。如何在全文中搜索Clob字段?
从dbms_lob.instr(a.a,\’k \’,1,1)0中选择* *。 76。如何显示当前连接
家庭?
显示用户
77。如何查看数据文件放置的路径?
col file_name格式a50
sqlSelect tableSpace_name,file_id,bytes/1024/1024,file_name
dba_data_files
file_id订购;
78。如何查看现有的回滚片段及其状态?
SQLCOL段格式A30
sqlSelect segment_name,所有者,tableSpace_name,sement_id,file
_id,状态来自
dba_rollback_segs
79。如何更改字段初始定义的检查范围?
sqlalter表xxx drop限制约束; name;
稍后创建一个新约束:
sqlalter表xxx添加约束约束check();
80。Oracle中常用的系统文件是什么?
这些文件信息通过以下视图显示:v $数据库,v $ datafile,v $ logfile v $ controlfile
V $参数;
81。内部连接内连接?
从bsempms a,bsdptms b中选择a。* a.dpt_no=b.dpt_no;
82。如何外部连接?
从bsempms a,bsdptms b中选择a。* a.dpt_no=b.dpt_no(+);
从bsempms a,bsdptms b wherea.dpt_no(+)=b.dpt_no中选择a。
83。如何执行脚本SQL文件?
sql@$ path/filename.sql;
84。如何快速清理一张大桌子?
sqltruncate table table_name;
85。如何检查有多少个数据库实例?
sqlSelect *来自v $实例;
86。如何查询数据库中有多少个表?
sqlSelect *来自all_tables;
87。如何测试SQL语句执行所需的时间?
SQLSET时机开启;
sqlSelect * tableName;
88。chr()的逆函数是什么?
ascii()
从Dual中选择char(65);
从Dual中选择ASCII(\’a \');
89。串联串联
从表中选择Concat(Col1,Col2);
从表中选择col1 || col2;
90。如何将Select的结果导入文本文件?
sqlspool c: \\ abcd.txt;
sqlSelect *从表格;
SQL启动;
91。如何估计SQL执行的I/OS的数量?
sqlset autotrace on;
sqlSelect *从表格;
或者
sqlSelect *来自v $ fileStat;
您可以查看IO号码
92。如何更改SQLPLU下的场大小?
Alter Table_name修改(field_name varchar2(100));
更改为大型企业,更改为小型企业,不要更改为小型企业(除非全部为空)
93。如何查询一定一天的数据?
从table_name中选择* trunc(date field)=to_date(\’2003-05-02 \’,\’yyyy-mm--
dd \’);
94。如何在SQL声明中插入全年?
创建桌子bsyear(d日期);
插入BSYEAR
选择to_date(\’20030101 \’,\’yyyymmdd \’)+rownum-1
来自all_objects
其中rownum=to_char(to_date(\’20031231 \’,\’yyyymmdd \’),\’ddd \’);
95。如果您修改表名称?
alter表old_table_name重命名为new_table_name;
96。如何获得命令的返回状态值?
sqlcode=0
97。如何知道用户拥有的权限?
从dba_sys_privs选择* *
98。从互联网下载的Oracle9i与在市场上出售的标准版本之间有什么区别?
功能没有差异,但是Oracle公司有明确的法规;从网站下载的甲骨文产品
不允许使用
商业用途,否则侵权。
99。如何确定数据库是在存档模式下还是在非架构模式下运行?
输入dbastudio,过程- >数据库- >存档查看。
100。sqlstartup pfile和ifile之间有什么区别?
PFILE是文本格式的Oracle的传统初始化参数文件。
ifile与C语言相似,用于将另一个文件引入
SPFILE是9i中添加的新参数文件,是默认参数文件,以二进制格式
启动后,只有Pfiles可用
101。如何搜索第一个N记录?
从员工中选择* empno的Rownum n命令;
102。如何知道机器上有多少并发用户Oracle支持?
sqlconn内部;
SQLShow参数过程;
103。db_block_size可以修改吗?
通常不可能,不建议这样做。
104。如何计算两个表中的记录总数?
从aa中选择(从aa中选择计数(id))+(从bb中选择计数(id))dual的总数;
105。如何使用SQL语句找到列中的第n个最大值?
从中选择*
(从员工中选择t。
其中等级=n;
106。如何在现有日期增加2年? ((
从dual中选择add_months(sysdate,24);
107。使用_ublk是什么意思,如果它为负?
它是\“无害”。
108。连接字符串是什么意思?
它应该是tnsnames.ora中服务名称之后的内容
109。如何扩大重做日志的大小?
创建一个临时重组组,然后切换日志,删除上一个日志,然后创建一个新日志。
110。表空间不大于4G?
没有限制。
111。返回大于或等于n的最小整数值?
从双重选择Ceil(n);
112。返回最小整数值小于或等于n?
从双重选择地板(n);
113.返回到当月的最后一天?
从dual中选择last_day(sysdate);
114。如何在不同用户之间导入数据?
Imp System/Manager file=aa.dmp frofuser=user_old touser=user_new
行=y索引=y
115。如何找到数据库表的主要密钥字段的名称?
sqlSelect *来自user_constraints,其中constraint_type=\’p \’和
table_name=\’table_name \’;
116。添加两个结果集的函数彼此?
sqlSelect *来自bsempms_old Intersect select * select *来自bsempms_new;
sqlSelect *来自bsempms_old Union * select * select *来自bsempms_new;
sqlSelect *来自bsempms_old Union从bsempms_new select * select *;
117。减去两个结果集的函数?
sqlSelect *来自bsempms_old minus select * select * select * from bsempms_new;
118。如何配置序列?
创建序列seq_custid
创建序列seq_custid启动1增量1;
表创建时间:
创建桌子
{
…}
插入:时
插入桌子
值(seq_cust.nextval,…)
日期每个部分的常用写作方法
119。如何在时间点:写这一年
从dual中选择to_char(sysdate,\’yyyy \’);
120。如何在时间点:写一个月
从dual中选择to_char(sysdate,\’mm \');
121。如何在时间点:写下一天
从dual中选择to_char(sysdate,\’dd \');
122。如何在时间点写入时间:
从dual中选择to_char(sysdate,\’hh24 \');
123。如何写时间点:
从dual中选择to_char(sysdate,\’mi \');
124。如何在时间点:写秒
从dual中选择to_char(sysdate,\'ss \');
125。如何写入时间点:的日期
从双重选择trunc(sysdate);
126。如何在时间点:写入时间
从dual中选择to_char(sysdate,\’HH24:MI:SS \’);
127。日期和时间形式成为角色形式
从dual中选择to_char(sysdate);
128。将字符串转换为日期或时间表格:
从Dual中选择to_date(\’2003/08/01 \’); 129。如何写返回参数:的一周中的一天
选择to_char(sysdate,\’d \’)来回
M DUAL;
130>. 返回参数一年中的第几天的写法 :
SELECT TO_CHAR(SYSDATE,\’DDD\’) FROM DUAL;
131>. 返回午夜和参数中指定的时间值之间的秒数的写法 :
SELECT TO_CHAR(SYSDATE,\’SSSSS\’) FROM DUAL;
132>. 返回参数中一年的第几周的写法 :
SELECT TO_CHAR(SYSDATE,\’WW\’) FROM DUAL;
虚拟字段
133. CURRVAL 和 nextval
为表创建序列
CREATE SEQUENCE EMPSEQ … ;
SELECT empseq.currval FROM DUAL ;
自动插入序列的数值
INSERT INTO emp
VALUES (empseq.nextval, \’LEWIS\’, \’CLERK\’,
7902, SYSDATE, 1200, NULL, 20) ;
134. ROWNUM
按设定排序的行的序号
SELECT * FROM emp WHERE ROWNUM< 10 ;
135. ROWID
返回行的物理地址
SELECT ROWID, ename FROM emp WHERE deptno = 20 ;
136. 将 N 秒转换为时分秒格式?
set serverout on
declare
N number := 1000000;
ret varchar2(100);
begin
ret := trunc(n/3600) || \’ 小时 \’ || to_char(to_date(mod(n,3600),\’sssss\’),\’fmmi\” 分
\”ss\” 秒 \”\’) ;
dbms_output.put_line(ret);
end;
137. 如何查询做比较大的排序的进程?
SELECT b.tablespace, b.segfile#, b.segblk#, b.blocks, a.sid, a.serial#,
a.username, a.osuser, a.status
FROM v$session a,v$sort_usage b
WHERE a.saddr = b.session_addr
ORDER BY b.tablespace, b.segfile#, b.segblk#, b.blocks ;
138. 如何查询做比较大的排序的进程的 SQL 语句?
select /*+ ORDERED */ sql_text from v$sqltext a
where a.hash_value = (
select sql_hash_value from v$session b
where b.sid = &sid and b.serial# = &serial)
order by piece asc ;
139. 如何查找重复记录?
SELECT * FROM TABLE_NAME
WHERE ROWID!=(SELECT MAX(ROWID) FROM TABLE_NAME D
WHERE TABLE_NAME.COL1=D.COL1 AND TABLE_NAME.COL2=D.COL2);
140. 如何删除重复记录?
DELETE FROM TABLE_NAME
WHERE ROWID!=(SELECT MAX(ROWID) FROM TABLE_NAME D
WHERE TABLE_NAME.COL1=D.COL1 AND TABLE_NAME.COL2=D.COL2);
141. 如何快速编译所有视图?
SQL >SPOOL VIEW1.SQL
SQL >SELECT \’ALTER VIEW \’||TNAME||\’
COMPILE;\’ FROM TAB;
SQL >SPOOL OFF
然后执行 VIEW1.SQL 即可。
SQL >@VIEW1.SQL;
142. ORA-01555 SNAPSHOT TOO OLD 的解决办法
增加 MINEXTENTS 的值,增加区的大小,设置一个高的 OPTIMAL 值。
143. 事务要求的回滚段空间不够,表现为表空间用满( ORA-01560 错误),回滚段
扩展到达
参数 MAXEXTENTS 的值( ORA-01628 )的解决办法 .
向回滚段表空间添加文件或使已有的文件变大;增加 MAXEXTENTS 的值。
144. 如何加密 ORACLE 的存储过程?
下列存储过程内容放在 AA.SQL 文件中
create or replace procedure testCCB(i in number) as
begin
dbms_output.put_line(\’ 输入参数是 \’||to_char(i));
end;
SQL>wrap iname=a.sql;
PL/SQL Wrapper: Release 8.1.7.0.0 – Production on Tue Nov 27 22:26:48 2001
Copyright (c) Oracle Corporation 1993, 2000. All Rights Reserved.
Processing AA.sql to AA.plb
运行 AA.plb
SQL>@AA.plb ;
145. 如何监控事例的等待?
select event,sum(decode(wait_Time,0,0,1)) \”Prev\”,
sum(decode(wait_Time,0,1,0)) \”Curr\”,count(*) \”Tot\”
from v$session_Wait
group by event order by 4;
146. 如何回滚段的争用情况?
select name, waits, gets, waits/gets \”Ratio\”
from v$rollstat C, v$rollname D
where C.usn = D.usn;
147. 如何监控表空间的 I/O 比例?
select B.tablespace_name name,B.file_name \”file\”,A.phyrds pyr,
A.phyblkrd pbr,A.phywrts pyw, A.phyblkwrt pbw
from v$filestat A, dba_data_files B
where A.file# = B.file_id
order by B.tablespace_name;
148. 如何监控文件系统的 I/O 比例?
select substr(C.file#,1,2) \”#\”, substr(C.name,1,30) \”Name\”,
C.status, C.bytes, D.phyrds, D.phywrts
from v$datafile C, v$filestat D
where C.file# = D.file#;
149. 如何在某个用户下找所有的索引?
select user_indexes.table_name, user_indexes.index_name,uniqueness,
column_name
from user_ind_columns, user_indexes
where user_ind_columns.index_name = user_indexes.index_name
and user_ind_columns.table_name = user_indexes.table_name
order by user_indexes.table_type, user_indexes.table_name,
user_indexes.index_name, column_position;
150. 如何监控 SGA 的命中率?
select a.value + b.value \”logical_reads\”, c.value \”phys_reads\”,
round(100 * ((a.value+b.value)-c.value) / (a.value+b.value)) \”BUFFER HIT RATIO\”
from v$sysstat a, v$sysstat b, v$sysstat c
where a.statistic# = 38 and b.statistic# = 39
and c.statistic# = 40;
151. 如何监控 SGA 中字典缓冲区的命中率?
select parameter, gets,Getmisses , getmisses/(gets+getmisses)*100 \”miss ratio\”,
(1-(sum(getmisses)/ (sum(gets)+sum(getmisses))))*100 \”Hit ratio\”
from v$rowcache
where gets+getmisses<>0
group by parameter, gets, getmisses;
152. 如何监控 SGA 享缓存区的命中率,应该小于 1% ?
select sum(pins) \”Total Pins\”, sum(reloads) \”Total Reloads\”,
sum(reloads)/sum(pins) *100 libcache
from v$librarycache;
select sum(pinhits-reloads)/sum(pins) \”hit radio\”,sum(reloads)/sum(pins)
\”reload
percent\”
from v$librarycache;
153. 如何显示所有数据库对象的类别和大小?
select count(name) num_instances ,type ,sum(source_size) source_size ,
sum(parsed_size) parsed_size ,sum(code_size) code_size ,sum(error_size)
error_size,
sum(source_size) +sum(parsed_size) +sum(code_size) +sum(error_size)
size_required
from dba_object_size
group by type order by 2;
154. 监控 SGA 中重做日志缓存区的命中率,应该小于 1%
SELECT name, gets, misses, immediate_gets, immediate_misses,
Decode(gets,0,0,misses/gets*100) ratio1,
Decode(immediate_gets+immediate_misses,0,0,
immediate_misses/(immediate_gets+immediate_misses) *100) ratio2
FROM v$latch WHERE name IN (\’redo allocation\’, \’redo copy\’);
155. 监控内存和硬盘的排序比率,最好使它小于 .10 ,增加 sort_area_size
SELECT name, value FROM v$sysstat WHERE name IN (\’sorts (memory)\’, \’sorts
(disk)\’);
156. 如何监控当前数据库谁在运行什么 SQL 语句?
SELECT osuser, username, sql_text from v$session a, v$sqltext b
where a.sql_address =b.address order by address, piece;
157. 如何监控字典缓冲区?
SELECT (SUM(PINS – RELOADS)) / SUM(PINS) \”LIB CACHE\” FROM
V$LIBRARYCACHE;
SELECT (SUM(GETS – GETMISSES – USAGE – FIXED)) / SUM(GETS) \”ROW CACHE\”
FROM
V$ROWCACHE;
SELECT SUM(PINS) \”EXECUTIONS\”, SUM(RELOADS) \”CACHE MISSES WHILE
EXECUTING\” FROM
V$LIBRARYCACHE;
后者除以前者 , 此比率小于 1%, 接近 0% 为好。
SELECT SUM(GETS) \”DICTIONARY GETS\”,SUM(GETMISSES) \”DICTIONARY CACHE
GET MISSES\”
FROM V$ROWCACHE
158. 监控 MTS
select busy/(busy+idle) \”shared servers busy\” from v$dispatcher;
此值大于 0.5 时,参数需加大
select sum(wait)/sum(totalq) \”dispatcher waits\” from v$queue where
type=\’dispatcher\’;
select count(*) from v$dispatcher;
select servers_highwater from v$mts;
servers_highwater 接近 mts_max_servers 时,参数需加大
159. 如何知道当前用户的 ID 号 ?
SQL>SHOW USER;
OR
SQL>select user from dual;
160. 如何查看碎片程度高的表 ?
SELECT segment_name table_name , COUNT(*) extents
FROM dba_segments WHERE owner NOT IN (\’SYS\’, \’SYSTEM\’) GROUP BY
segment_name
HAVING COUNT(*) = (SELECT MAX( COUNT(*) ) FROM dba_segments GROUP BY
segment_name);
162. 如何知道表在表空间中的存储情况 ?
select segment_name,sum(bytes),count(*) ext_quan from dba_extents where
tablespace_name=\’&tablespace_name\’ and segment_type=\’TABLE\’ group by
tablespace_name,segment_name;
163. 如何知道索引在表空间中的存储情况 ?
select segment_name,count(*) from dba_extents where segment_type=\’INDEX\’ and
owner=\’&owner\’
group by segment_name;

164 、如何知道使用 CPU 多的用户 session?
11 是 cpu used by this session
select a.sid,spid,status,substr(a.program,1,40)
prog,a.terminal,osuser,value/60/100 value
from v$session a,v$process b,v$sesstat c
where c.statistic#=11 and c.sid=a.sid and a.paddr=b.addr order by value desc;
165. 如何知道监听器日志文件 ?
以 8I 为例
$ORACLE_HOME/NETWORK/LOG/LISTENER.LOG
166. 如何知道监听器参数文件 ?
以 8I 为例
$ORACLE_HOME/NETWORK/ADMIN/LISTENER.ORA
167. 如何知道 TNS 连接文件 ?
以 8I 为例
$ORACLE_HOME/NETWORK/ADMIN/TNSNAMES.ORA
168. 如何知道 Sql*Net 环境文件 ?
以 8I 为例
$ORACLE_HOME/NETWORK/ADMIN/SQLNET.ORA
169. 如何知道警告日志文件 ?
以 8I 为例
$ORACLE_HOME/ADMIN/SID/BDUMP/SIDALRT.LOG
170. 如何知道基本结构 ?
以 8I 为例
$ORACLE_HOME/RDBMS/ADMIN/STANDARD.SQL
171. 如何知道建立数据字典视图 ?
以 8I 为例
$ORACLE_HOME/RDBMS/ADMIN/CATALOG.SQL
172. 如何知道建立审计用数据字典视图 ?
以 8I 为例
$ORACLE_HOME/RDBMS/ADMIN/CATAUDIT.SQL
173. 如何知道建立快照用数据字典视图 ?
以 8I 为例
$ORACLE_HOME/RDBMS/ADMIN/CATSNAP.SQL
本讲主要讲的是 SQL 语句的优化方法 ! 主要基于 ORACLE9I 的 .
174. /*+ALL_ROWS*/
表明对语句块选择基于开销的优化方法 , 并获得最佳吞吐量 , 使资源消耗最小化 .
例如 :
SELECT /*+ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE
EMP_NO=\’CCBZZP\’;
175. /*+FIRST_ROWS*/
表明对语句块选择基于开销的优化方法 , 并获得最佳响应时间 , 使资源消耗最小化 .
例如 :
SELECT /*+FIRST_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE
EMP_NO=\’CCBZZP\’; 176. /*+CHOOSE*/
表明如果数据字典中有访问表的统计信息 , 将基于开销的优化方法 , 并获得最佳的吞吐
量 ;
表明如果数据字典中没有访问表的统计信息 , 将基于规则开销的优化方法 ;
例如 :
SELECT /*+CHOOSE*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE
EMP_NO=\’CCBZZP\’;
177. /*+RULE*/
表明对语句块选择基于规则的优化方法 .
例如 :
SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE
EMP_NO=\’CCBZZP\’;
178. /*+FULL(TABLE)*/
表明对表选择全局扫描的方法 .
例如 :
SELECT /*+FULL(A)*/ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE
EMP_NO=\’CCBZZP\’;
179. /*+ROWID(TABLE)*/
提示明确表明对指定表根据 ROWID 进行访问 .
例如 :
SELECT /*+ROWID(BSEMPMS)*/ * FROM BSEMPMS WHERE
ROWID>=\’AAAAAAAAAAAAAA\’
AND EMP_NO=\’CCBZZP\’;
180. /*+CLUSTER(TABLE)*/
提示明确表明对指定表选择簇扫描的访问方法 , 它只对簇对象有效 .
例如 :
SELECT /*+CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS
WHERE DPT_NO=\’TEC304\’ AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;
181. /*+INDEX(TABLE INDEX_NAME)*/
表明对表选择索引的扫描方法 .
例如 :
SELECT /*+INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE
FEWMALE
BSEMPMS */ FROM BSEMPMS WHERE SEX=\’M\’;
182. /*+INDEX_ASC(TABLE INDEX_NAME)*/
表明对表选择索引升序的扫描方法
Oracle数据库开发规范指南
1 编码规范1.1 SQL语句规范1) SQL 语句中的所有表名、字段名全部小写。
2) 连接符OR、IN、AND、以及=、<=、>=等前后加上一个空格。当语句中出现括号时,括号的两边不留空格。
3) “不等于”统一使用\”<>\”。虽然\”!=\”和\”<>\”是等价的,为了统一,不等于一律使用\”<>\”表示。
4) 关键字、保留字预留到左边起始位置,一行有多列,超过80 个字符时,基于列对齐原则,采用下行缩进。
5) SQL中的字符类型数据应该统一使用单引号。特别对纯数字的字串,必须用单引号,否则会导致内部转换而引起性能问题或索引失效问题。利用trim(),lower()等函数格式化匹配条件。
6) 对于非常复杂的sql(特别是有多层嵌套,带子句或相关查询的),应该先考虑是否设计不当引起的。对于一些复杂SQL可以考虑使用程序实现。
7) SQL语句用大写,因为oracle总是先解析SQL语句,把小写的字母转换成大写的再执行。
例如:
SELECTA.ORDER_ID,A.ORDER_CONTENT
FROMCRM_ORDER A,CRM_PRODUCT B
WHEREA.ORDER_ID = B.FK_ORDER_ID
ANDA.ORDER_ID = \’A001\’
/*关键字、保留字、函数左对齐、首字母尽量大写*/
1.2 SELECT语句的格式标准SELECT Column_name1,Column_name2
INTO :Parameter1,:Parameter2
FROM Table1 A, Table2 B
WHERE A.Column_name = B.Column_name;
在写查询语句的时候,要求语句的条件排列先后顺序要考虑语句执行的性能,要做到语句能很好的利用到现有的索引,一般原则上不允许在一条查询语句中有超过五张以上的表进行关联,因为当超过五张表关联时Oracle将不再做语句的优化处理;对语句的性能可以通过“执行计划”来跟踪,在PL/SQL Developer中的Explain Plan Window中可分析ORACLE的执行计划。
以下是SELECT的语法,详细的使用方法请查阅相关资料
SELECT [DISTINCT | ALL] {* | column1[, column2]…}
FROM {table_1 | (subquery)} [alias]
[, {table_2 | (subquery)} [alias]]…
[WHERE condition]
[CONNECT BY condition [START WITH condition]
[GROUP BY expn] [HAVING expn]
[{ UNION [ALL] | INTERSECT | MINUS } SELECT . . . ]
[ ORDER BY [expn ] [ ASC | DESC]
[ FOR UPDATE [OF [user.]table | view] column ]
[NOWAIT]
1.3 UPDATE语句的格式标准UPDATE Table SET Column_name1 = :Parameter1,
Column_name2 = :Parameter2
WHERE Column_name = :Pareameter;
以下是UPDATE的语法,详细的使用方法请查阅相关资料
UPDATE [user.]table[@db_link][alias]
SET { column1=express1[,column2=experss2]…|
(column1[,column2]…)=(subquery) }
[WHERE condition|current of cursor];
1.4 INSERT语句的格式标准INSERT INTO TableName(Column_name1,Column_name2)
VALUES(:Parameter1,:Parameter2)
或
INSERT INTO TableName(Column_name1,Column_name2)
SELECT Column_name1,Column_name2
FROM Table
WHERE Column_name = :Pareameter
不允许出现 INSERT INTO TableName VALUES (:Parameter1,:Parameter2)的写法。
也不允许出现 INSERT INTO TableName SELECT * FROM Table 的写法。
1.5 DELETE语句的格式标准DELETE FROM TableName WHERE Column_name1 = :Parameter1
以下是DELETE的语法,详细的使用方法请查阅相关资料
DELETE [ FROM ] [user.]table [@db_link][Alias] [WHERE condition];
1.6 UNION,INTERSECT及MINUS有时需要从多个表中组合具有一种相似类型的信息。Union 可以完成将两个以上的表的相类似的查询结果合并在一起,并且相同的只取其一;如果union all 则表示返回所有行(不管是否重复)。Intersect返回在两个表中都有相同内容的信息。Minus 则返回只在一个表中出现的信息
1. 语法:
select …
union[all]
select …
select …
intersect
select …
select …
minus
select …
以上语句进行连表操作,而表同表的字段顺序的类型相同但字段标题名可不同,使用ordey by时后面如果是字段名,要求所有的表的字段标题名相同,否则用字段的顺序号
例如:selectid,name,year from user1
unionselectno,name,to_number(null) year from user2
order by1,name,year
union all ,union,Minus,INTERSECT 比较
其中union all 效率最好
Union 集合并,不包含重复行
Union all 集合并,可以包含重复行
Minus 集合差,不包含重复行
INTERSECT 集合交,不包含重复行
逻辑分表,大表分成若干小表假设一个表的数据有2 千万行,我们可以针对这样给划分成5 个逻辑分表,每个分为500
万行的数据,对于历史数据我们可以分成这样的表来操作,例如一年12 个月每个月都可以
创建一个表.
orderr_01
…
order_12;
这样一个大表就可以有效地分成12 个表的操作,尤其可以按时间或者按地域划分的都可以
这样操作。
可以创建视图连接多张表:
CREATE VIEW v_union_reginfo
AS
SELECT * FROM reginfo
UNION ALL
SELECT * FROM reginfo_temp
1.7 常用语句编写规范例如: 条件语句IFGA >5
THENDBMS_OUTPUT.PUT_LINE(\’AAA\’);
ELSIFGA< 20
THENDBMS_OUTPUT.PUT_LINE(\’BBB\’);
ELSEDBMS_OUTPUT.PUT_LINE(\’CCC\’);
END IF;/*语句开闭位置对应*/
例如: 循环语句A:FORI
IN0..10
LOOPDBMS_OUTPUT.PUT_LINE(I);
END LOOP;/*LOOP 和END LOOP 结束*/
例如: 循环语句B:DECLAREGA
NUMBER(3):=10;
BEGINWHILEGA<100
LOOPDBMS_OUTPUT.PUT_LINE(GA);
GA := GA+1;
END LOOP;END;/*LOOP 和END LOOP 结束*/
1.8 游标规范例如:
OPENCUR_CRM_ORDER
LOOPFETCHCUR_CRM_ORDER
INTOEXIT WHERECUR_CRM_ORDER%
NOTFOUND;
…
END LOOP;CLOSECUR_CRM_ORDER;
/*程序中显示使用游标的规范*/
带参数的游标写法
DECLAREvregnick
VARCHAR(20):=\’张三\’;
vregid
INT;
cursorcu_a(var1
VARCHAR2)
ISSELECTregid
INTOvregid
FROMreginfo
WHEREregnick=vregnick;
BEGINOPENcu_a(vregnick) ;
LoopFETCHcu_a
INTOvregid;
EXITWHENcu_a%
NOTFOUND;
dbms_output.put_line(vregid);
END LOOP;
CLOSEcu_a;
END;Ref 游标,绑定变量的游标的下发
DECLARETYPEcur_type
IS REF CURSOR;cur cur_type;
vregnick VARCHAR(20):=\’张三\’;
vreginfo reginfo%
ROWTYPE;
BEGINOPENcur
FOR\’select * from reginfo where regnick=:a\’
USINGvregnick;
LoopFETCHcur
INTOvreginfo;
EXIT Whencur%
NOTFOUND;
dbms_output.put_line(vreginfo.regid);
END LOOP;CLOSEcur;
END;
2 注释规范2.1 代码注释规范每个存储过程、触发器、包、函数的开头要有详细的说明,包括程序的名称、参数、功能、返回值以及编写时间、编写人员等信息。每次修改在说明后面附上修改记录。
注释例子:
/*****************************************
模块名称:
模块功能:
创建人:
创建时间:
输入参数:
输出参数:
返回值:
修改历史:==============================
修改人:
修改时间:
修改说明:
***************************************/
2.2 注释位置要求1) 注释行的长度以在最大化窗口内可以看到全部内容为宜,如果一行不够显示需要换行,下一行注释语句与上一行注释语句应对齐。
2) 变量申明的注释放在变量申明语句的后面,并以– 为注释语句
3) 注释行一律放在被注释语句的上一行。
4) 注释行中,注释命令与注释内容空一个空格,如:
V_BpMode VARCHAR2(10) ; — 用户服务类型
BEGIN
— 选取用户服务类型
BEGIN
SELECT BpMode INTO V_BpMode FROM ACC_BP WHERE User_ID = I_User_ID ;
EXCEPTION
WHEN NO_DATA_FOUND THEN
WHEN OTHERS THEN
END ;
2.3 代码片断注释要求1) 当处理流程比较复杂,不容易让其它人看懂时,应该加以注释。
2) 注释行放在 被注释的代码片断上一行,并与代码片断第一行对齐。
3 编码命名规范3.1 命名语言命名应该使用英文单词,避免使用拼音,特别不应该使用拼音简写。命名不允许使用中文或者特殊字符。
英文单词使用对象本身意义相对或相近的单词。选择最简单或最通用的单词。不能使用毫不相干的单词来命名。
当一个单词不能表达对象含义时,用词组组合,如果组合太长时,采用简写或缩写,缩写要基本能表达原单词的意义。
当出现对象名重名时,是不同类型对象时,在对象名前后加类型前缀或后缀以示区别。
3.2 大小写名称一律大写,以方便不同数据库移植,以及避免程序调用问题。
3.3 单词分隔命名的各单词之间可以使用下划线“_”进行分隔。
3.4 保留字命名不允许使用SQL保留字。
3.5 命名长度表名、字段名、视图名长度应限制在20个字符内(含前缀)。
3.6 字段名称同一个字段名在一个数据库中只能代表一个意思。比如telephone在一个表中代表“电话号码”的意思,在另外一个表中就不能代表“手机号码”的意思。
不同的表用于相同内容的字段应该采用同样的名称,字段类型定义。
4 编码数据类型规范4.1 字符型固定长度的字串类型采用char,长度不固定的字串类型采用varchar。避免在长度不固定的情况下采用char类型。如果在数据迁移等出现以上情况,则必须使用trim()函数截去字串后的空格。
4.2 数字型数字型字段尽量采用number类型,要注意精度。
4.3 日期和时间4.3.1 系统时间由数据库产生的系统时间首选数据库的日期型,如DATE类型。
4.3.2 外部时间由数据导入或外部应用程序产生的日期时间类型采用varchar类型,数据格式采用:YYYYMMDDHH24MISS。
4.4 大字段如无特别需要,避免使用大字段(blob,clob,long,text,image等)。
4.5 唯一键对于数字型唯一键值,尽可能用系列sequence产生。
5 SQL编码技巧性能通常是指软件的“时间—空间”效率,而不仅是指软件的运行速度。人们总希望软件的运行速度快些,并且占用资源少些。
5.1 SELECT子句中避免使用Select * 语句如果不是必要取出所有数据,不要用*来代替,应给出字段列表,使用动态SQL列引用 \’*\’ 是一个方便的方法。但是,这是一个非常低效的方法。ORACLE在解析的过程中,会将\’*\’ 通过查询数据库系统的数据字典依次转换成所有对应的列名,这意味着将耗费更多的时间和系统资源。
注意别名的使用:SELECT A.ORDER_ID,A.ORDER_CONTENT FROM CRM_ORDER A
/*别名使用会加速查询速度*/
并行查询 ,效率可以提高第一种形式SELECT /*+ FULL(T) PARALLEL(T,2) */ * FROM CRM_ORDER T
/*多cpu 使用如上语句可以加速查询速度适合unix 服务器这样的配置*/
第二种形式SELECT /*+ PARALLEL(T,2) */ * FROM CRM_ORDER T
5.2 避免嵌套的Select子句这个实际上是In子句的特例。
5.3 使用SELECT COUNT(主键)计算表的记录数和一般的观点相反,count(*) 比count(1)稍快,当然如果可以通过索引检索,对索引列的计数仍旧是最快的。例如 COUNT(EMPNO)。如果没有主键SELECT COUNT(1) FROM stuinfo; 要快一些。
例如:
select count(*) from testtab
/*得到表testtab的记录数*/
select count(id) from testtab
/*得到表testtab id字段非空记录数*/
select count(distinct id) from testtab
/*得到表testtab id字段值非相同记录数*/
5.4 WHERE子句1) ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。
2) 除非你不考虑执行效率问题,否则请不要在WHERE 子句中使用函数作为条件。例如 WHERE TO_NUMBER(TEL_NBR)=7654321 这种写法是效率非常低下的。
3) 避免使用HAVING子句,HAVING 只会在检索出所有记录之后才对结果集进行过滤。 这个处理需要排序,总计等操作。如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。
4) 某些SELECT 语句中的WHERE子句不使用索引。
例如:
(1)‘!=\’ 将不使用索引。索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中。
!= 运算符的使用.不使用索引
SELECT * FROM reginfo WHERE regid != 8
使用索引
SELECT * FROM reginfo WHERE regid >8 OR regid< 8
(2) ‘||\’是字符连接函数. 就象其他函数那样, 停用了索引。
(3) ‘+\’是数学函数。就象其他数学函数那样, 停用了索引。
(4)相同的索引列不能互相比较,这将会启用全表扫描。
5.5 用TRUNCATE替代DELETE当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息。 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)。
而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息。当命令运行后,数据不能被恢复。因此很少的资源被调用,执行时间也会很短。
如果是删除全表,请使用TRUNCATE TABLE TabName 来替代 DELETE FROM TabName,能有效提高速度,并释放该表所占的存储空间,减少磁盘碎片。由于TRUNCATE TABLE是DDL语言,在存储过程中不能直接使用,应加上EXECUTE IMMEDIATE,使用方法如下:EXECUTE IMMEDIATE ‘TRUNCATE TABLE TabName’;
使用前请注意是否有权限问题。
5.6 尽量多使用COMMIT只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少 。
COMMIT所释放的资源:
1) 回滚段上用于恢复数据的信息。
2) 被程序语句获得的锁。
3) redo log buffer 中的空间。
4) ORACLE为管理上述3种资源中的内部花费。
5.7 关闭自动提交功能,提高系统性能 在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:
conn.setAutoCommit(false);
值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。
5.8 避免在索引列上使用IS NULL和IS NOT NULL任何SQL 语句,只要在where 子句中使用了is null 或is not null,那么Oracle 优化器就不允许使用索引了。
避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引。对于单列索引,如果列包含空值,索引中将不存在此记录。对于复合索引,如果每个列都为空,索引中同样不存在此记录。如果至少有一个列不为空,则记录存在于索引中。
例如: 如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入)。然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空. 因此你可以插入1000 条具有相同键值的记录,当然它们都是空! 因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引. 低效: (索引失效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; 高效: (索引有效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;
5.9 避免In子句使用In 或 not In子句时,特别是当子句中有多个值时,且查询数据表数据较多时,速度会明显下降。可以采用连接查询或外连接查询来提高性能。
Char 比 varchar 查询时高效。在进行查询及建立索引时,char比varchar的效率要高,当然varchar在存储上比char要好。
/*in 语句走得全表扫描*/SELECTFROMreginfo a
WHEREa.regid
IN (SELECT b.regid FROM reginfo_temp b)
/*正确的写法一:*/SELECTFROMreginfo a
WHEREEXISTS(SELECT 1 FROM reginfo_temp b WHERE a.regid=b.regid)
/*正确写法二:*/SELECTa.regid,a.regnick
FROMreginfo a
INNER JOINreginfo_temp b
ONa.regid=b.regid
/*正确写法三早期sql 写法:*/SELECTa.regid,a.regnick
FROMreginfo a, reginfo_temp b
WHEREa.regid=b.regid
5.10 用EXISTS替代IN在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率。
5.11 用NOT EXISTS替代NOT IN在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历)。为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。
我们在查询时经常在where 子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT 可用来对任何逻辑运算符号取反。
下面是一个NOT 子句的例子:
… where not (status =\’VALID\’)
如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT 运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即使不在查询where 子句中显式地加入NOT 词,NOT 仍在运算符中,见下例:
… where status<>\’INVALID\’;
再看下面这个例子:
select * from employee where salary<>3000;
对这个查询,可以改写为不使用NOT:
select * from employee where salary<3000 or salary>3000;
虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle 对salary 列使用索引,而第一种查询则不能使用索引。
有时候会将一列和一系列值相比较。最简单的办法就是在where 子句中使用子查询。在where 子句中可以使用两种格式的子查询。
第一种格式是使用IN 操作符:
… where column in(select * from … where …);
第二种格式是使用EXIST 操作符:
… where exists (select \’X\’ from …where …);
我相信绝大多数人会使用第一种格式,因为它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle 中可以几乎将所有的IN 操作符子查询改写为使用EXISTS 的子查询。
第二种格式中,子查询以‘select \’X\’开始。运用EXISTS 子句不管子查询从表中抽取什么数据它只查看where 子句。这样优化器就不必遍历整个表而仅根据索引就可完成工作(这里假定在where 语句中使用的列存在索引)。相对于IN 子句来说,EXISTS 使用相连子查询,构造起来要比IN 子查询困难一些。
通过使用EXIST,Oracle 系统会首先检查主查询,然后运行子查询直到它找到第一个匹配项,这就节省了时间。Oracle 系统在执行IN 子查询时,首先执行子查询,并将获得的结果列表存放在在一个加了索引的临时表中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中以后再执行主查询。这也就是使用EXISTS 比使用IN 通常查询速度快的原因。
同时应尽可能使用NOT EXISTS 来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),NOT EXISTS 要比NOT IN 查询效率更高。
5.12 用UNION替换OR (适用于索引列)通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。注意, 以上规则只针对多个索引列有效。如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低。
在下面的例子中, LOC_ID 和REGION上都建有索引。
高效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 UNION SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE REGION = “MELBOURNE”
低效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = “MELBOURNE”
如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面。
5.13 用UNION-ALL 替换UNION ( 如果有可能的话)当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序. 如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高. 需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是要从业务需求分析使用UNION ALL的可行性. UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这块内存的优化也是相当重要的。
5.14 用>=替代>高效:
SELECTFROMEMP
WHEREDEPTNO >=4
低效:
SELECTFROMEMP
WHEREDEPTNO >3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
5.15 Order by 语句ORDER BY 语句决定了Oracle 如何将返回的查询结果排序。Order by 语句对要排序的列没有什么特别的限制,也可以将函数加入列中(联接或者附加等)。任何在Order by 语句的非索引项或者有计算表达式都将降低查询速度。
仔细检查order by 语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by 语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by 子句中使用表达式。
避免不必要的排序,不必要的数据排序大大的降低系统性能。
5.16 避免带通配符(%)的like 语句同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton 的人。可以采用如下的查询SQL 语句:
selectfromemployee
wherelast_name like \’%cliton%\’;
这里由于通配符(%)在搜寻词首出现,所以Oracle 系统不使用last_name 的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:
selectfromemployee
wherelast_name like \’c%\’;
例如:
不使用索引情况:
SELECT * FROM temp_stuinfo WHERE stu_add LIKE \’%甘肃%\’;
使用索引情况
SELECT * FROM temp_stuinfo WHERE stu_add LIKE \’甘肃%\’;
可以使用oracle 函数instr 代替like 加快查询速度:
SELECT * FROM temp_stuinfo WHERE instr(stu_add ,\’肃\’)>0;
instr和like的性能比较:其实从效率角度来看,谁能用到索引,谁的查询速度就会快。
like有时可以用到索引,例如:name like ‘李%’,而当下面的情况时索引会失效:name like ‘%李’或者name like ‘%李%’。所以一般我们查找中文类似于‘%字符%’时,索引都会失效。与其他数据库不同的是,oracle支持函数索引。例如在name字段上建个instr索引,查询速度就比较快了,这也是为什么instr会比like效率高的原因。
注:instr(title,’手册’)>0 相当于like‘%手册%’
instr(title,’手册’)=0 相当于not like‘%手册%’
5.17 使用oracle全文检索对海量的文本数据进行搜索有很多时候,使用instr和like是很理想的, 特别是搜索仅跨越很小的表的时候。然而通过这些文本定位的方法将导致全表扫描,对资源来说消耗比较昂贵,而且实现的搜索功能也非常有限,因此对海量的文本数据进行搜索时,建议使用oralce提供的全文检索功能。
设置全文检索:
步骤一:检查和设置数据库角色
首先检查数据库中是否有CTXSYS用户和CTXAPP脚色。如果没有这个用户和角色,意味着你的数据库创建时未安装intermedia功能(10G默认安装都有此用户和角色)。你必须修改数据库以安装这项功能。默认安装情况下,ctxsys用户是被锁定的,因此要先启用ctxsys的用户。
步骤二:赋权
在ctxsys用户下,授予测试用户oratext以下权限:
1 GRANT resource, CONNECT, ctxapp TO oratext;
2 GRANT EXECUTE ON ctxsys.ctx_cls TO oratext;
3 GRANT EXECUTE ON ctxsys.ctx_ddl TO oratext;
4 GRANT EXECUTE ON ctxsys.ctx_doc TO oratext;
5 GRANT EXECUTE ON ctxsys.ctx_output TO oratext;
6 GRANT EXECUTE ON ctxsys.ctx_query TO oratext;
7 GRANT EXECUTE ON ctxsys.ctx_report TO oratext;
8 GRANT EXECUTE ON ctxsys.ctx_thes TO oratext;
9 GRANT EXECUTE ON ctxsys.ctx_ulexer TO oratext;
步骤三:设置词法分析器(lexer)
Oracle实现全文检索,其机制其实很简单。即通过Oracle专利的词法分析器(lexer),将文章中所有的表意单元(Oracle 称为 term)找出来,记录在一组以dr$开头的表中,同时记下该term出现的位置、次数、hash值等信息。检索时,Oracle从这组表中查找相应的term,并计算其出现频率,根据某个算法来计算每个文档的得分(score),即所谓的‘匹配率’。而lexer则是该机制的核心,它决定了全文检索的效率。Oracle针对不同的语言提供了不同的lexer,而我们通常能用到其中的三个:
basic_lexer:针对英语。它能根据空格和标点来将英语单词从句子中分离,还能自动将一些出现频率过高已经失去检索意义的单词作为‘垃圾’处理,如if,is等,具有较高的处理效率。但该lexer应用于汉语则有很多问题,由于它只认空格和标点,而汉语的一句话中通常不会有空格,因此,它会把整句话作为一个term,事实上失去检索能力。以‘中国人民站起来了’这句话为例,basic_lexer分析的结果只有一个term,就是‘中国人民站起来了’。此时若检索‘中国’,将检索不到内容。
chinese_vgram_lexer:专门的汉语分析器,支持所有汉字字符集(ZHS16CGB231280 ZHS16GBK ZHT32EUC ZHT16BIG5 ZHT32TRIS ZHT16MSWIN950 ZHT16HKSCS UTF8 )。该分析器按字为单元来分析汉语句子。‘中国人民站起来了’这句话,会被它分析成如下几个term:‘中’,‘中国’,‘国人’,‘人民’,‘民站’,‘站起’,起来’,‘来了’,‘了’。可以看出,这种分析方法,实现算法很简单,并且能实现‘一网打尽’,但效率则是差强人意。
chinese_lexer:这是一个新的汉语分析器,只支持utf8字符集。上面已经看到,chinese vgram lexer这个分析器由于不认识常用的汉语词汇,因此分析的单元非常机械,像上面的‘民站’,‘站起’在汉语中根本不会单独出现,因此这种term是没有意义的,反而影响效率。chinese_lexer的最大改进就是该分析器能认识大部分常用汉语词汇,因此能更有效率地分析句子,像以上两个愚蠢的单元将不会再出现,极大提高了效率。但是它只支持utf8,如果你的数据库是zhs16gbk字符集,则只能使用笨笨的那个Chinese vgram lexer。如果不做任何设置,Oracle缺省使用basic_lexer这个分析器。
要指定使用哪一个lexer,可以这样操作:
第一.建立一个preference:
1EXEC ctx_ddl.create_preference (\’my_lexer\’, \’chinese_vgram_lexer\’);
第二.在建立全文索引时,指明所用的lexer:
1CREATE INDEX myindex ON mytable(mycolumn) indextype IS ctxsys.context parameters(\’lexer my_lexer\’);
这样建立的全文检索索引,就会使用chinese_vgram_lexer作为分析器。
全文检索举例:
测试用户为oratext,建立此用户和对应表空间的内容就不写了:
步骤一:授权,ctxsys登陆并对oratext用户授权:
GRANT resource, CONNECT, ctxapp TO oratext;
GRANT EXECUTE ON ctxsys.ctx_cls TO oratext;
GRANT EXECUTE ON ctxsys.ctx_ddl TO oratext;
GRANT EXECUTE ON ctxsys.ctx_doc TO oratext;
GRANT EXECUTE ON ctxsys.ctx_output TO oratext;
GRANT EXECUTE ON ctxsys.ctx_query TO oratext;
GRANT EXECUTE ON ctxsys.ctx_report TO oratext;
GRANT EXECUTE ON ctxsys.ctx_thes TO oratext;
GRANT EXECUTE ON ctxsys.ctx_ulexer TO oratext;
步骤二:设置词法分析器,使用chinese_vgram_lexer作为分析器:
BEGIN
–设置词法分析器
ctx_ddl.create_preference (\’oratext_lexer\’, \’chinese_vgram_lexer\’);
END;
可以通过下面的语句查看系统默认及设置的oracle text参数:
SELECT pre_name, pre_object FROM ctx_preferences
可以看到我刚刚设置的语法分析器参数oratext_lexer,(默认的有一个MY_LEXER的语法分析器参数)。
步骤三:建立测试表,插入测试数据:
CREATE TABLE textdemo(
id NUMBER NOT NULL PRIMARY KEY,
book_author varchar2(20),–作者
publish_time DATE,–发布日期
title varchar2(400),–标题
book_abstract varchar2(2000),–摘要
path varchar2(200)–路径
);
commit;
INSERT INTO textdemo VALUES(1,\’宫琦峻\’,to_date(\’2008-10-07\’,\’yyyy-mm-dd\’),\’ 移动城堡\’,\’故事发生在19世纪末的欧洲,善良可爱的苏菲被恶毒的女巫施下魔咒,从18岁的女孩变成90岁的婆婆,孤单无助的她无意中走入镇外的移动城堡,据说它的主人哈尔以吸取女孩的灵魂为乐,但是事情并没有人们传说的那么可怕,性情古怪的哈尔居然收留了苏菲,两个人在四脚的移动城堡中开始了奇妙的共同生活,一段交织了爱与痛、乐与悲的爱情故事在战火中悄悄展开\’,\’E:\\textsearch\\moveingcastle.doc\’);
INSERT INTO textdemo VALUES(2,\’莫贝克曼贝托夫\’,to_date(\’2008-10-07\’,\’yyyy-mm-dd\’),\’ 转弯\’,\’这部由俄罗斯导演提莫贝克曼贝托夫执导的影片自6 月末在北美上映以来,已经在全球取得了超过3亿美元的票房收入。在亚洲上映后也先后拿下日本、韩国等地的票房冠军宝座。虽然不少网友在此之前也相继通过各种渠道接触到本片,但相信影片凭着在大银幕上呈现出的超酷的视听效果,依然能够吸引大量影迷前往影院捧场。\’,\’E:\\textsearch\\catch.pdf\’);
INSERT INTO textdemo VALUES(3,\’袁泉\’,to_date(\’2008-10-07\’,\’yyyy-mm-dd\’),\’主演吴彦祖和袁泉现身\’,\’电影《如梦》在上海同乐坊拍摄,主演吴彦祖和袁泉现身。由于是深夜拍摄,所以周围并没有过多的fans注意到,给了剧组一个很清净的拍摄环境,站在街头的袁泉低着头,在寒冷的夜里看上去还真有些像女鬼,令人毛骨悚然。\’,\’E:\\textsearch\\dream.txt\’);
commit;
步骤四:在book_abstract字段建立索引使用刚刚设置的ORATEXT_LEXER :chinese_vgram_lexer作为分析器。
CREATE INDEX demo_abstract ON textdemo(book_abstract) indextype IS ctxsys.context parameters(\’lexer ORATEXT_LEXER\’);
commit;
之后如上所述多出很多dr$开头的表和索引,系统会创建四个相关的表:
DR$DEMO_ABSTRACT$I(分词后的TOKEN表)\\DR$DEMO_ABSTRACT$K\\DR$DEMO_ABSTRACT$N \\DR$DEMO_ABSTRACT$R
下面的语句可以查看索引创建过程中是否发生了错误:
SELECT * FROM ctx_USER_index_errors
附:对于建立索引的类型(例如ctxsys.context),包括四种:context,ctxcat,ctxrule,ctxxpath。
CONTEXT用于对含有大量连续文本数据进行检索。支持word、html、xml、text等很多数据格式。支持范围(range)分区,支持并行创建索引(Parallel indexing)的索引类型。
支持类型:VARCHAR2, CLOB, BLOB, CHAR, BFILE, XMLType, and URIType.DML。操作后,需要CTX_DDL.SYNC_INDEX手工同步索引如果有查询包含多个词语,直接用空格隔开(如 oracle itpub)。
查询标识符CONTAINS
CTXCAT适用于混合查询语句(如查询条件包括产品id,价格,描述等)。适合于查询较小的具有一定结构的文本段。具有事务性。DML 操作后,索引会自动进行同步。
操作符:and,or,>,;<, =,between,in
查询标识符CATSEARCH
CTXRULE查询标识符MATCHES。
CTXXPATH(这两个索引没有去更多搜索相关内容)
一般来说我们建立CONTEXT类型的索引(CONTAINS来查询)。
步骤五:查询测试
–查询或
SELECT score(20),t.* FROM textdemo t WHERE contains(book_abstract,\’移动城堡 or 俄罗斯\’,20)>0;
SELECT score(20),t.* FROM textdemo t WHERE contains(book_abstract,\’移动城堡 or 欧洲\’,20)>0;
–基本查询
SELECT score(20),t.* FROM textdemo t WHERE contains(book_abstract,\’移动城堡\’,20)>0;
–查询包含多个词语and测试通过
SELECT score(20),t.* FROM textdemo t WHERE contains(book_abstract,\’移动城堡 and 欧洲\’,20)>0;
测试通过。
5.18 使用CREATE TABLE AS 替代INSERT INTO…select如果可能,请使用

CREATE TABLE TabName AS SELECT * FROM ……
来替代
INSERT INTO TabName SELECT * FROM …… ,
特别是在记录数比较多的情况下,前者的速度上会有非常明显的优势。
5.19 用索引提高效率索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引。同样在联接多个表时使用索引也可以提高效率。另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证。那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列。通常, 在大型表中使用索引特别有效。当然,你也会发现,在扫描小表时,使用索引同样能提高效率。虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价。索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改。这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O 。因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。
/*定期的重构索引是有必要的 */
ALTER INDEXREBUILD必要的时候可以强制使用索引SELECT/*+ INDEX(CRM_ORDER INDEX_A) */ *
FROMCRM_ORDER A
/*强制使用索引可以加快查询速度*/
索引对SQL性能的提高:
/*不使用索引的情况,如果在字段regtime 上创建了索引:*/
SELECTFROMreginfo
WHEREto_char(regtime,\’yyyy-mm-dd\’) =\’2013-01-01\’;
这时候上面语句就没有走索引.
换成如下语句:
SELECTFROMreginfo
WHEREregtime = to_date(\’2013-01-01\’,\’yyyy-mm-dd\’);
效果就好些.
或者创建函数索引.
CREATE INDEXindex_abc
ONreginfo(to_char(regtime,\’yyyy-mm-dd\’));
组合索引(索引字段最好不超过三个)举例:如果我们在查询中使用两列作为条件,就可以创建组合索引
CREATE INDEXindex_com
ONreginfo(regnick,regtime)
例子:
SELECTFROMreginfo
WHEREregnick=\’张三\’ AND regtime=to_date(\’2013-1-1\’,\’yyyy-mm-dd\’);
/*使用了索引index_com*/
SELECTFROMreginfo
WHEREregnick=\’张三\’ 也使用了索引index_com
如果我们在regnick 和regtime 上面分别创建了单列索引,通过联合查询后,效率也没有组合索引好些。
如果下查询
SELECTFROMreginfo
WHEREregtime=to_date(\’2013-1-1\’,\’yyyy-mm-dd\’)
这时使用了组合索引的第二个字段,但是oracle9i 以后就使用了跳跃式索引,如果加上优化就是用了跳跃式索引:
例子:
SELECT/*+index(reginfo index_com)*/*
FROMreginfo
WHEREregtime=to_date(\’2013-1-1\’,\’yyyy-mm-dd\’);
跳跃索引比全表扫描也快,但是要慢于单列索引,所以有必要创建单列索引在此列上。
nvl 相同索引列不能互相比较/*不使用索引的情况:*/
SELECTFROMreginfo
WHEREregnick = nvl(regnick,\’张三\’);
/*使用索引的情况*/
SELECTFROMreginfo
WHEREregnick LIKE nvl(regnick,\’%\’);
/*不要在索引列上使用运算*/SELECTFROMtemp_stuinfo
WHEREsubstr(stu_name,1,1)=\’张\’;
5.20 选择最有效率的表名顺序(只在基于规则的优化器中有效)ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理。在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。当ORACLE处理多个表时, 会运用排序及合并的方式连接它们。首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表。
5.21 共享SQL语句为了不重复解析相同的SQL语句,在第一次解析之后,ORACLE将SQL语句存放在内存中。这块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内存可以被所有的数据库用户共享。 因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同, ORACLE就能很快获得已经被解析的语句以及最好的执行路径。ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用。
可惜的是ORACLE只对简单的表提供高速缓冲(cache buffering),这个功能并不适用于多表连接查询。
数据库管理员必须在init.ora中为这个区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大了。
当你向ORACLE提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句。这里需要注明的是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须完全相同(包括空格,换行等)。
共享的语句必须满足三个条件:
a) 字符级的比较: 当前被执行的语句和共享池中的语句必须完全相同。
b) 两个语句所指的对象必须完全相同。
c) 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)。
6 健壮性健壮性是指在异常情况下,软件能够正常运行的能力。正确性与健壮性的区别是:前者描述软件在需求范围之内的行为,后者描述软件在需求范围之外的行为。想不到异常情况,把异常错当正常而不作处理,这些都会降低健壮性。提高软件的健壮性也是开发者的义务。
健壮性有两层含义:一是容错能力,二是恢复能力。
容错是指发生异常情况时系统不出错误的能力。高风险系统如航空航天、武器、金融等领域的系统,容错性设计非常重要。
容错是非常健壮的意思。而恢复则是指软件发生错误后(不论死活)重新运行时,能否恢复到没有发生错误前的状态的能力。
从语义上理解,恢复不及容错那么健壮。
6.1 在创建存储过程语句中提供必要的参数创建存储过程语句可以包含很多参数,虽然从语法角度讲它们不是必须的,但是在创建存储过程时提供这些参数可以提高执行效率。
6.2 对输入参数进行必要的的检查和预处理无论使用哪种编程语言,对输入参数的判断都是必须的。正确的参数验证是保证程序良好运行的前提。
正确的验证和预处理操作包括:
如果输入参数错误,存储过程应返回一个明确的值告诉客户应用,然后客户应用可以根据返回的值进行处理,或者向存储过程提交新的参数,或者去调用其他的程序。
根据业务逻辑,对输入参数作一定的预处理,如大小写的转换,NULL与空字符串或0的转换等。
6.3 异常处理在存储过程执行的过程中,经常因为数据或者其他问题产生异常(condition)。根据业务逻辑,存储过程应该对异常进行相应处理或直接返回给调用者。此处暂且将condition译为异常以方便理解。实际上有些异常(condition)并非是由于错误引起的。
6.4 PL/SQL 异常处理规范对于存储过程、函数等程序块都要有异常处理部分,以提高程序的自检能力。
例如:
EXCEPTIONWHENNO_DATA_FOUND
THENDBMS_OUTPUT.PUT_LINE(
SQLERRM);
6.5 在 PL/SQL 中使用 sqlcode,sqlerrm当存储过程执行出错抛出EXCEPTION时,可通过 sqlcode 和 sqlerrm取得当前的ORACLE错误代码和错误信息,以下是使用范例:
DECLARE
V_SQLCODE number(6);
V_ERRMSG varchar2(512);
BEGIN
UPDATE dept SET username = (SELECT username FROM work_group)
WHERE rownum=1;
EXCEPTION
WHEN OTHERS THEN
V_SQLCODE := sqlcode;
V_ERRMSG := sqlerrm;
Dmbs_output.put_line (‘程序出错,错误代码:’|| V_SQLCODE||’错误信息:’|| V_ERRMSG);
END;
6.6 后台验证异常信息规范使用数据库过程、函数进行后台数据验证时,发现异常情况,需要记录异常,并返回到用户界面。
异常信息描述要求简洁、准确、完整,揭示异常实质,准确定位异常出现的位置。
异常分为警告和错误两类。
由于每个实际项目,业务不同,异常信息也变化很大。每个项目开始时根据需求,统一进行定义。
6.7 Insert语句健壮性使用Insert语句一定要给出要插入值的字段列表,这样即使更改了表结构加了字段也不会影响现有系统的运行。
6.8 外键值可用null的问题外键列如没有明确说明not null,可插入null记录(而null是在外部表的记录中没有的),如无可插null记录的想法,要对外键字段加not null约束。
6.9 序列 sequence 跳号的问题sequence 因回滚,系统崩溃(使用cache 内的值将认为已用),多表引用都将使其跳号,所以不能用于连续序号 。
7 安全性和完整性约束无论在使用Select,还是使用破坏力极大的Update和Delete语句时,一定要检查Where条件判断的完整性,不要在运行时出现数据的重大丢失。
如果不确定,最好先用Select语句带上相同条件来验证一下结果集,来检验条件是否正确。
有依赖关系的表,例如主外键关系表,在删除父表时必须级联删除其子表相应数据,或则按照某种业务规则转移该数据。9I中表中字段缩小及变类型,字段为空或表空,varchar和char长度不变可任意改,字段名和表名字段可用 ALTER TABLE table SET UNUSED (column) 设定为不可用,注意无命令再设为可用。
百万级数据库优化方案–Oracle 性能优化总结
文章阅读前,请各位老铁关注,转发,点赞
一、百万级数据库优化方案
1.对查询优化,尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
复制
最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.
备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。
不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
复制
复制
3.应尽量避免在 where 子句中使用 != 或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:
复制
复制
复制
对于连续的数值,能用 between 就不要用 in 了:
复制
复制
很多时候用 exists 代替 in 是一个好的选择:
复制
用下面的语句替换:
复制
6.下面的查询也将导致全表扫描:
复制
若要提高效率,可以考虑全文检索。
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
复制
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:
复制
这类代码不会返回任何结果集,但是会消耗系统资源的
13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
二、数据库访问性能优化特别说明:
1、 本文只是面对数据库应用开发的程序员,不适合专业DBA,DBA在数据库性能优化方面需要了解更多的知识;
2、 本文许多示例及概念是基于Oracle数据库描述,对于其它关系型数据库也可以参考,但许多观点不适合于KV数据库或内存数据库或者是基于SSD技术的数据库;
3、 本文未深入数据库优化中最核心的执行计划分析技术。
读者对像:
开发人员:如果你是做数据库开发,那本文的内容非常适合,因为本文是从程序员的角度来谈数据库性能优化。
架构师:如果你已经是数据库应用的架构师,那本文的知识你应该清楚90%,否则你可能是一个喜欢折腾的架构师。
DBA(数据库管理员):大型数据库优化的知识非常复杂,本文只是从程序员的角度来谈性能优化,DBA除了需要了解这些知识外,还需要深入数据库的内部体系架构来解决问题。
在网上有很多文章介绍数据库优化知识,但是大部份文章只是对某个一个方面进行说明,而对于我们程序员来说这种介绍并不能很好的掌握优化知识,因为很多介绍只是对一些特定的场景优化的,所以反而有时会产生误导或让程序员感觉不明白其中的奥妙而对数据库优化感觉很神秘。
很多程序员总是问如何学习数据库优化,有没有好的教材之类的问题。在书店也看到了许多数据库优化的专业书籍,但是感觉更多是面向DBA或者是PL/SQL开发方面的知识,个人感觉不太适合普通程序员。而要想做到数据库优化的高手,不是花几周,几个月就能达到的,这并不是因为数据库优化有多高深,而是因为要做好优化一方面需要有非常好的技术功底,对操作系统、存储硬件网络、数据库原理等方面有比较扎实的基础知识,另一方面是需要花大量时间对特定的数据库进行实践测试与总结。
作为一个程序员,我们也许不清楚线上正式的服务器硬件配置,我们不可能像DBA那样专业的对数据库进行各种实践测试与总结,但我们都应该非常了解我们SQL的业务逻辑,我们清楚SQL中访问表及字段的数据情况,我们其实只关心我们的SQL是否能尽快返回结果。那程序员如何利用已知的知识进行数据库优化?如何能快速定位SQL性能问题并找到正确的优化方向?
面对这些问题,笔者总结了一些面向程序员的基本优化法则,本文将结合实例来坦述数据库开发的优化知识。
要正确的优化SQL,我们需要快速定位能性的瓶颈点,也就是说快速找到我们SQL主要的开销在哪里?而大多数情况性能最慢的设备会是瓶颈点,如下载时网络速度可能会是瓶颈点,本地复制文件时硬盘可能会是瓶颈点,为什么这些一般的工作我们能快速确认瓶颈点呢,因为我们对这些慢速设备的性能数据有一些基本的认识,如网络带宽是2Mbps,硬盘是每分钟7200转等等。因此,为了快速找到SQL的性能瓶颈点,我们也需要了解我们计算机系统的硬件基本性能指标,下图展示的当前主流计算机性能指标数据。
从图上可以看到基本上每种设备都有两个指标:
延时(响应时间):表示硬件的突发处理能力;
带宽(吞吐量):代表硬件持续处理能力。
从上图可以看出,计算机系统硬件性能从高到代依次为:
CPU——Cache(L1-L2-L3)——内存——SSD硬盘——网络——硬盘
由于SSD硬盘还处于快速发展阶段,所以本文的内容不涉及SSD相关应用系统。
根据数据库知识,我们可以列出每种硬件主要的工作内容:
CPU及内存:缓存数据访问、比较、排序、事务检测、SQL解析、函数或逻辑运算;
网络:结果数据传输、SQL请求、远程数据库访问(dblink);
硬盘:数据访问、数据写入、日志记录、大数据量排序、大表连接。
根据当前计算机硬件的基本性能指标及其在数据库中主要操作内容,可以整理出如下图所示的性能基本优化法则:
这个优化法则归纳为5个层次:
1、 减少数据访问(减少磁盘访问)
2、 返回更少数据(减少网络传输或磁盘访问)
3、 减少交互次数(减少网络传输)
4、 减少服务器CPU开销(减少CPU及内存开销)
5、 利用更多资源(增加资源)
由于每一层优化法则都是解决其对应硬件的性能问题,所以带来的性能提升比例也不一样。传统数据库系统设计是也是尽可能对低速设备提供优化方法,因此针对低速设备问题的可优化手段也更多,优化成本也更低。我们任何一个SQL的性能优化都应该按这个规则由上到下来诊断问题并提出解决方案,而不应该首先想到的是增加资源解决问题。
以下是每个优化法则层级对应优化效果及成本经验参考:
接下来,我们针对5种优化法则列举常用的优化手段并结合实例分析。
数据块是数据库中数据在磁盘中存储的最小单位,也是一次IO访问的最小单位,一个数据块通常可以存储多条记录,数据块大小是DBA在创建数据库或表空间时指定,可指定为2K、4K、8K、16K或32K字节。下图是一个Oracle数据库典型的物理结构,一个数据库可以包括多个数据文件,一个数据文件内又包含多个数据块;
ROWID是每条记录在数据库中的唯一标识,通过ROWID可以直接定位记录到对应的文件号及数据块位置。ROWID内容包括文件号、对像号、数据块号、记录槽号,如下图所示:
三、数据库访问优化法则详解数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少,即使是专业的DBA也不一定能完全做到最优。
索引会大大增加表记录的DML(INSERT,UPDATE,DELETE)开销,正确的索引可以让性能提升100,1000倍以上,不合理的索引也可能会让性能下降100倍,因此在一个表中创建什么样的索引需要平衡各种业务需求。
索引常见问题:
索引有哪些种类?常见的索引有B-TREE索引、位图索引、全文索引,位图索引一般用于数据仓库应用,全文索引由于使用较少,这里不深入介绍。B-TREE索引包括很多扩展类型,如组合索引、反向索引、函数索引等等,以下是B-TREE索引的简单介绍:
B-TREE索引也称为平衡树索引(Balance Tree),它是一种按字段排好序的树形目录结构,主要用于提升查询性能和唯一约束支持。B-TREE索引的内容包括根节点、分支节点、叶子节点。
叶子节点内容:索引字段内容+表记录ROWID
根节点,分支节点内容:当一个数据块中不能放下所有索引字段数据时,就会形成树形的根节点或分支节点,根节点与分支节点保存了索引树的顺序及各层级间的引用关系。
一个普通的BTREE索引结构示意图如下所示:
如果我们把一个表的内容认为是一本字典,那索引就相当于字典的目录,如下图所示:
图中是一个字典按部首+笔划数的目录,相当于给字典建了一个按部首+笔划的组合索引。
一个表中可以建多个索引,就如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。
一个索引也可以由多个字段组成,称为组合索引,如上图就是一个按部首+笔划的组合目录。
SQL什么条件会使用索引?当字段上建有索引时,通常以下情况会使用索引:
INDEX_COLUMN = ?
INDEX_COLUMN >?
INDEX_COLUMN >= ?
INDEX_COLUMN< ?
INDEX_COLUMN<= ?
INDEX_COLUMN between ? and ?
INDEX_COLUMN in (?,?,…,?)
INDEX_COLUMN like ?||\’%\’(后导模糊查询)
T1. INDEX_COLUMN=T2. COLUMN1(两个表通过索引字段关联)
SQL什么条件不会使用索引?我们一般在什么字段上建索引?这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。主键及外键通常都要有索引,其它需要建索引的字段应满足以下条件:
1、字段出现在查询条件中,并且查询条件可以使用索引;
2、语句执行频率高,一天会有几千次以上;
3、通过字段条件可筛选的记录集很小,
那数据筛选比例是多少才适合?这个没有固定值,需要根据表数据量来评估,以下是经验公式,可用于快速评估:
小表(记录数小于10000行的表):筛选比例<10%;大表:(筛选返回记录数)<(表总记录数*单条记录长度)/10000/16单条记录长度≈字段平均内容长度之和+字段数*2以下是一些字段是否需要建B-TREE索引的经验分类:
如何知道SQL是否使用了正确的索引?简单SQL可以根据索引使用语法规则判断,复杂的SQL不好办,判断SQL的响应时间是一种策略,但是这会受到数据量、主机负载及缓存等因素的影响,有时数据全在缓存里,可能全表访问的时间比索引访问时间还少。要准确知道索引是否正确使用,需要到数据库中查看SQL真实的执行计划,这个话题比较复杂,详见SQL执行计划专题介绍。
索引对DML(INSERT,UPDATE,DELETE)附加的开销有多少?这个没有固定的比例,与每个表记录的大小及索引字段大小密切相关,以下是一个普通表测试数据,仅供参考:
索引对于Insert性能降低56%
索引对于Update性能降低47%
索引对于Delete性能降低29%
因此对于写IO压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。
有些时候,我们只是访问表中的几个字段,并且字段内容较少,我们可以为这几个字段单独建立一个组合索引,这样就可以直接只通过访问索引就能得到数据,一般索引占用的磁盘空间比表小很多,所以这种方式可以大大减少磁盘IO开销。
如:
复制
如果这个SQL经常使用,我们可以在type,id,name上创建组合索引
复制
有了这个组合索引后,SQL就可以直接通过my_comb_index索引返回数据,不需要访问company表。
还是拿字典举例:有一个需求,需要查询一本汉语字典中所有汉字的个数,如果我们的字典没有目录索引,那我们只能从字典内容里一个一个字计数,最后返回结果。如果我们有一个拼音目录,那就可以只访问拼音目录的汉字进行计数。如果一本字典有1000页,拼音目录有20页,那我们的数据访问成本相当于全表访问的50分之一。
切记,性能优化是无止境的,当性能可以满足需求时即可,不要过度优化。在实际数据库中我们不可能把每个SQL请求的字段都建在索引里,所以这种只通过索引访问数据的方法一般只用于核心应用,也就是那种对核心表访问量最高且查询字段数据量很少的查询。
SQL执行计划是关系型数据库最核心的技术之一,它表示SQL执行时的数据访问算法。由于业务需求越来越复杂,表数据量也越来越大,程序员越来越懒惰,SQL也需要支持非常复杂的业务逻辑,但SQL的性能还需要提高,因此,优秀的关系型数据库除了需要支持复杂的SQL语法及更多函数外,还需要有一套优秀的算法库来提高SQL性能。
目前ORACLE有SQL执行计划的算法约300种,而且一直在增加,所以SQL执行计划是一个非常复杂的课题,一个普通DBA能掌握50种就很不错了,就算是资深DBA也不可能把每个执行计划的算法描述清楚。虽然有这么多种算法,但并不表示我们无法优化执行计划,因为我们常用的SQL执行计划算法也就十几个,如果一个程序员能把这十几个算法搞清楚,那就掌握了80%的SQL执行计划调优知识。
由于篇幅的原因,SQL执行计划需要专题介绍,在这里就不多说了。
一般数据分页方式有:
将数据从应用服务器全部下载到本地应用程序或浏览器,在应用程序或浏览器内部通过本地代码进行分页处理
优点:编码简单,减少客户端与应用服务器网络交互次数
缺点:首次交互时间长,占用客户端内存
适应场景:客户端与应用服务器网络延时较大,但要求后续操作流畅,如手机GPRS,超远程访问(跨国)等等。
将数据从数据库服务器全部下载到应用服务器,在应用服务器内部再进行数据筛选。以下是一个应用服务器端Java程序分页的示例:
复制
优点:编码简单,只需要一次SQL交互,总数据与分页数据差不多时性能较好。
缺点:总数据量较多时性能较差。
适应场景:数据库系统不支持分页处理,数据量较小并且可控。
采用数据库SQL分页需要两次SQL完成
一个SQL计算总数量
一个SQL返回分页后的数据
优点:性能好
缺点:编码复杂,各种数据库语法不同,需要两次SQL交互。
oracle数据库一般采用rownum来进行分页,常用分页语法有如下两种:
直接通过rownum分页:复制
数据访问开销=索引IO+索引全部记录结果对应的表数据IO
采用rowid分页语法优化原理是通过纯索引找出分页记录的ROWID,再通过ROWID回表返回数据,要求内层查询和排序字段全在索引里。
复制
数据访问开销=索引IO+索引分页结果对应的表数据IO
实例:
一个公司产品有1000条记录,要分页取其中20个产品,假设访问公司索引需要50个IO,2条记录需要1个表数据IO。
那么按第一种ROWNUM分页写法,需要550(50+1000/2)个IO,按第二种ROWID分页写法,只需要60个IO(50+20/2);
通过去除不必要的返回字段可以提高性能,例:
复制
优点:
1、减少数据在网络上传输开销
2、减少服务器数据处理开销
3、减少客户端内存占用
4、字段变更时提前发现问题,减少程序BUG
5、如果访问的所有字段刚好在一个索引里面,则可以使用纯索引访问提高性能。
缺点:增加编码工作量
由于会增加一些编码工作量,所以一般需求通过开发规范来要求程序员这么做,否则等项目上线后再整改工作量更大。
如果你的查询表中有大字段或内容较多的字段,如备注信息、文件内容等等,那在查询表时一定要注意这方面的问题,否则可能会带来严重的性能问题。如果表经常要查询并且请求大内容字段的概率很低,我们可以采用分表处理,将一个大表分拆成两个一对一的关系表,将不常用的大内容字段放在一张单独的表中。如一张存储上传文件的表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
我们可以分拆成两张一对一的关系表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
T_FILECONTENT(ID, FILE_CONTENT)
通过这种分拆,可以大大提少T_FILE表的单条记录及总大小,这样在查询T_FILE时性能会更好,当需要查询FILE_CONTENT字段内容时再访问T_FILECONTENT表。
数据库访问框架一般都提供了批量提交的接口,jdbc支持batch的提交处理方法,当你一次性要往一个表中插入1000万条数据时,如果采用普通的executeUpdate处理,那么和服务器交互次数为1000万次,按每秒钟可以向数据库服务器提交10000次估算,要完成所有工作需要1000秒。如果采用批量提交模式,1000条提交一次,那么和服务器交互次数为1万次,交互次数大大减少。采用batch操作一般不会减少很多数据库服务器的物理IO,但是会大大减少客户端与服务端的交互次数,从而减少了多次发起的网络延时开销,同时也会降低数据库的CPU开销。
假设要向一个普通表插入1000万数据,每条记录大小为1K字节,表上没有任何索引,客户端与数据库服务器网络是100Mbps,以下是根据现在一般计算机能力估算的各种batch大小性能对比值:
从上可以看出,Insert操作加大Batch可以对性能提高近8倍性能,一般根据主键的Update或Delete操作也可能提高2-3倍性能,但不如Insert明显,因为Update及Delete操作可能有比较大的开销在物理IO访问。以上仅是理论计算值,实际情况需要根据具体环境测量。
很多时候我们需要按一些ID查询数据库记录,我们可以采用一个ID一个请求发给数据库,如下所示:
复制
我们也可以做一个小的优化, 如下所示,用ID INLIST的这种方式写SQL:
复制
通过这样处理可以大大减少SQL请求的数量,从而提高性能。那如果有10000个ID,那是不是全部放在一条SQL里处理呢?答案肯定是否定的。首先大部份数据库都会有SQL长度和IN里个数的限制,如
ORACLE的IN里就不允许超过1000个值。
另外当前数据库一般都是采用基于成本的优化规则,当IN数量达到一定值时有可能改变SQL执行计划,从索引访问变成全表访问,这将使性能急剧变化。随着SQL中IN的里面的值个数增加,SQL的执行计划会更复杂,占用的内存将会变大,这将会增加服务器CPU及内存成本。
评估在IN里面一次放多少个值还需要考虑应用服务器本地内存的开销,有并发访问时要计算本地数据使用周期内的并发上限,否则可能会导致内存溢出。
综合考虑,一般IN里面的值个数超过20个以后性能基本没什么太大变化,也特别说明不要超过100,超过后可能会引起执行计划的不稳定性及增加数据库CPU及内存成本,这个需要专业DBA评估。
当我们采用select从数据库查询数据时,数据默认并不是一条一条返回给客户端的,也不是一次全部返回客户端的,而是根据客户端fetch_size参数处理,每次只返回fetch_size条记录,当客户端游标遍历到尾部时再从服务端取数据,直到最后全部传送完成。所以如果我们要从服务端一次取大量数据时,可以加大fetch_size,这样可以减少结果数据传输的交互次数及服务器数据准备时间,提高性能。
以下是jdbc测试的代码,采用本地数据库,表缓存在数据库CACHE中,因此没有网络连接及磁盘IO开销,客户端只遍历游标,不做任何处理,这样更能体现fetch参数的影响:
复制
测试示例中的employee表有100000条记录,每条记录平均长度135字节
以下是测试结果,对每种fetchsize测试5次再取平均值:
Oracle jdbc fetchsize默认值为10,由上测试可以看出fetchsize对性能影响还是比较大的,但是当fetchsize大于100时就基本上没有影响了。fetchsize并不会存在一个最优的固定值,因为整体性能与记录集大小及硬件平台有关。根据测试结果建议当一次性要取大量数据时这个值设置为100左右,不要小于40。注意,fetchsize不能设置太大,如果一次取出的数据大于JVM的内存会导致内存溢出,所以建议不要超过1000,太大了也没什么性能提高,反而可能会增加内存溢出的危险。
注:图中fetchsize在128以后会有一些小的波动,这并不是测试误差,而是由于resultset填充到具体对像时间不同的原因,由于resultset已经到本地内存里了,所以估计是由于CPU的L1,L2 Cache命中率变化造成,由于变化不大,所以笔者也未深入分析原因。
iBatis的SqlMapping配置文件可以对每个SQL语句指定fetchsize大小,如下所示:
复制
大型数据库一般都支持存储过程,合理的利用存储过程也可以提高系统性能。如你有一个业务需要将A表的数据做一些加工然后更新到B表中,但是又不可能一条SQL完成,这时你需要如下3步操作:
a:将A表数据全部取出到客户端;
b:计算出要更新的数据;
c:将计算结果更新到B表。
如果采用存储过程你可以将整个业务逻辑封装在存储过程里,然后在客户端直接调用存储过程处理,这样可以减少网络交互的成本。
当然,存储过程也并不是十全十美,存储过程有以下缺点:
a、不可移植性,每种数据库的内部编程语法都不太相同,当你的系统需要兼容多种数据库时最好不要用存储过程。
b、学习成本高,DBA一般都擅长写存储过程,但并不是每个程序员都能写好存储过程,除非你的团队有较多的开发人员熟悉写存储过程,否则后期系统维护会产生问题。
c、业务逻辑多处存在,采用存储过程后也就意味着你的系统有一些业务逻辑不是在应用程序里处理,这种架构会增加一些系统维护和调试成本。
d、存储过程和常用应用程序语言不一样,它支持的函数及语法有可能不能满足需求,有些逻辑就只能通过应用程序处理。
e、如果存储过程中有复杂运算的话,会增加一些数据库服务端的处理成本,对于集中式数据库可能会导致系统可扩展性问题。
f、为了提高性能,数据库会把存储过程代码编译成中间运行代码(类似于java的class文件),所以更像静态语言。当存储过程引用的对像(表、视图等等)结构改变后,存储过程需要重新编译才能生效,在24*7高并发应用场景,一般都是在线变更结构的,所以在变更的瞬间要同时编译存储过程,这可能会导致数据库瞬间压力上升引起故障(Oracle数据库就存在这样的问题)。
个人观点:普通业务逻辑尽量不要使用存储过程,定时性的ETL任务或报表统计函数可以根据团队资源情况采用存储过程处理。
要通过优化业务逻辑来提高性能是比较困难的,这需要程序员对所访问的数据及业务流程非常清楚。
举一个案例:
某移动公司推出优惠套参,活动对像为VIP会员并且2010年1,2,3月平均话费20元以上的客户。
那我们的检测逻辑为:
复制
如果我们修改业务逻辑为:
复制
通过这样可以减少一些判断vip_flag的开销,平均话费20元以下的用户就不需要再检测是否VIP了。
如果程序员分析业务,VIP会员比例为1%,平均话费20元以上的用户比例为90%,那我们改成如下:
复制
这样就只有1%的VIP会员才会做检测平均话费,最终大大减少了SQL的交互次数。
以上只是一个简单的示例,实际的业务总是比这复杂得多,所以一般只是高级程序员更容易做出优化的逻辑,但是我们需要有这样一种成本优化的意识。
现在大部分Java框架都是通过jdbc从数据库取出数据,然后装载到一个list里再处理,list里可能是业务Object,也可能是hashmap。
由于JVM内存一般都小于4G,所以不可能一次通过sql把大量数据装载到list里。为了完成功能,很多程序员喜欢采用分页的方法处理,如一次从数据库取1000条记录,通过多次循环搞定,保证不会引起JVM Out of memory问题。
以下是实现此功能的代码示例,t_employee表有10万条记录,设置分页大小为1000:
复制
以上代码实际执行时间为6.516秒
很多持久层框架为了尽量让程序员使用方便,封装了jdbc通过statement执行数据返回到resultset的细节,导致程序员会想采用分页的方式处理问题。实际上如果我们采用jdbc原始的resultset游标处理记录,在resultset循环读取的过程中处理记录,这样就可以一次从数据库取出所有记录。显著提高性能。
这里需要注意的是,采用resultset游标处理记录时,应该将游标的打开方式设置为FORWARD_READONLY模式(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY),否则会把结果缓存在JVM里,造成JVM Out of memory问题。
代码示例:
复制
调整后的代码实际执行时间为3.156秒
从测试结果可以看出性能提高了1倍多,如果采用分页模式数据库每次还需发生磁盘IO的话那性能可以提高更多。
iBatis等持久层框架考虑到会有这种需求,所以也有相应的解决方案,在iBatis里我们不能采用queryForList的方法,而应用该采用queryWithRowHandler加回调事件的方式处理,如下所示:
复制
iBatis的queryWithRowHandler很好的封装了resultset遍历的事件处理,效果及性能与resultset遍历一样,也不会产生JVM内存溢出。
绑定变量是指SQL中对变化的值采用变量参数的形式提交,而不是在SQL中直接拼写对应的值。
非绑定变量写法:
复制
绑定变量写法:
复制
Java中Preparestatement就是为处理绑定变量提供的对像,绑定变量有以下优点:
1、防止SQL注入
2、提高SQL可读性
3、提高SQL解析性能,不使用绑定变更我们一般称为硬解析,使用绑定变量我们称为软解析。
第1和第2点很好理解,做编码的人应该都清楚,这里不详细说明。关于第3点,到底能提高多少性能呢,下面举一个例子说明:
假设有这个这样的一个数据库主机:
2个4核CPU
100块磁盘,每个磁盘支持IOPS为160
业务应用的SQL如下:
select * from table where pk=?
这个SQL平均4个IO(3个索引IO+1个数据IO)
IO缓存命中率75%(索引全在内存中,数据需要访问磁盘)
SQL硬解析CPU消耗:1ms (常用经验值)
SQL软解析CPU消耗:0.02ms(常用经验值)
假设CPU每核性能是线性增长,访问内存Cache中的IO时间忽略,要求计算系统对如上应用采用硬解析与采用软解析支持的每秒最大并发数:
从以上计算可以看出,不使用绑定变量的系统当并发达到8000时会在CPU上产生瓶颈,当使用绑定变量的系统当并行达到16000时会在磁盘IO上产生瓶颈。所以如果你的系统CPU有瓶颈时请先检查是否存在大量的硬解析操作。
使用绑定变量为何会提高SQL解析性能,这个需要从数据库SQL执行原理说明,一条SQL在Oracle数据库中的执行过程如下图所示:
当一条SQL发送给数据库服务器后,系统首先会将SQL字符串进行hash运算,得到hash值后再从服务器内存里的SQL缓存区中进行检索,如果有相同的SQL字符,并且确认是同一逻辑的SQL语句,则从共享池缓存中取出SQL对应的执行计划,根据执行计划读取数据并返回结果给客户端。
如果在共享池中未发现相同的SQL则根据SQL逻辑生成一条新的执行计划并保存在SQL缓存区中,然后根据执行计划读取数据并返回结果给客户端。
为了更快的检索SQL是否在缓存区中,首先进行的是SQL字符串hash值对比,如果未找到则认为没有缓存,如果存在再进行下一步的准确对比,所以要命中SQL缓存区应保证SQL字符是完全一致,中间有大小写或空格都会认为是不同的SQL。
如果我们不采用绑定变量,采用字符串拼接的模式生成SQL,那么每条SQL都会产生执行计划,这样会导致共享池耗尽,缓存命中率也很低。
一些不使用绑定变量的场景:
a、数据仓库应用,这种应用一般并发不高,但是每个SQL执行时间很长,SQL解析的时间相比SQL执行时间比较小,绑定变量对性能提高不明显。数据仓库一般都是内部分析应用,所以也不太会发生SQL注入的安全问题。
b、数据分布不均匀的特殊逻辑,如产品表,记录有1亿,有一产品状态字段,上面建有索引,有审核中,审核通过,审核未通过3种状态,其中审核通过9500万,审核中1万,审核不通过499万。
要做这样一个查询:
select count(*) from product where status=?
采用绑定变量的话,那么只会有一个执行计划,如果走索引访问,那么对于审核中查询很快,对审核通过和审核不通过会很慢;如果不走索引,那么对于审核中与审核通过和审核不通过时间基本一样;
对于这种情况应该不使用绑定变量,而直接采用字符拼接的方式生成SQL,这样可以为每个SQL生成不同的执行计划,如下所示。
复制
Oracle的排序算法一直在优化,但是总体时间复杂度约等于nLog(n)。普通OLTP系统排序操作一般都是在内存里进行的,对于数据库来说是一种CPU的消耗,曾在PC机做过测试,单核普通CPU在1秒钟可以完成100万条记录的全内存排序操作,所以说由于现在CPU的性能增强,对于普通的几十条或上百条记录排序对系统的影响也不会很大。但是当你的记录集增加到上万条以上时,你需要注意是否一定要这么做了,大记录集排序不仅增加了CPU开销,而且可能会由于内存不足发生硬盘排序的现象,当发生硬盘排序时性能会急剧下降,这种需求需要与DBA沟通再决定,取决于你的需求和数据,所以只有你自己最清楚,而不要被别人说排序很慢就吓倒。
以下列出了可能会发生排序操作的SQL语法:
Order by
Group by
Distinct
Exists子查询
Not Exists子查询
In子查询
Not In子查询
Union(并集),Union All也是一种并集操作,但是不会发生排序,如果你确认两个数据集不需要执行去除重复数据操作,那请使用Union All 代替Union。
Minus(差集)
Intersect(交集)
Create Index
Merge Join,这是一种两个表连接的内部算法,执行时会把两个表先排序好再连接,应用于两个大表连接的操作。如果你的两个表连接的条件都是等值运算,那可以采用Hash Join来提高性能,因为Hash Join使用Hash 运算来代替排序的操作。具体原理及设置参考SQL执行计划优化专题。
我们SQL的业务逻辑经常会包含一些比较操作,如a=b,a
Like模糊查询,如下所示:a like ‘%abc%’
Like模糊查询对于数据库来说不是很擅长,特别是你需要模糊检查的记录有上万条以上时,性能比较糟糕,这种情况一般可以采用专用Search或者采用全文索引方案来提高性能。
不能使用索引定位的大量In List,如下所示:a in (:1,:2,:3,…,:n) —-n>20
如果这里的a字段不能通过索引比较,那数据库会将字段与in里面的每个值都进行比较运算,如果记录数有上万以上,会明显感觉到SQL的CPU开销加大,这个情况有两种解决方式:
a、 将in列表里面的数据放入一张中间小表,采用两个表Hash Join关联的方式处理;
b、 采用
str2varList方法将字段串列表转换一个临时表处理,关于
str2varList方法可以在网上直接查询,这里不详细介绍。
以上两种解决方案都需要与中间表Hash Join的方式才能提高性能,如果采用了Nested Loop的连接方式性能会更差。
如果发现我们的系统IO没问题但是CPU负载很高,就有可能是上面的原因,这种情况不太常见,如果遇到了最好能和DBA沟通并确认准确的原因。
什么是复杂运算,一般我认为是一秒钟CPU只能做10万次以内的运算。如含小数的对数及指数运算、三角函数、3DES及BASE64数据加密算法等等。
如果有大量这类函数运算,尽量放在客户端处理,一般CPU每秒中也只能处理1万-10万次这样的函数运算,放在数据库内不利于高并发处理。
多进程并行访问是指在客户端创建多个进程(线程),每个进程建立一个与数据库的连接,然后同时向数据库提交访问请求。当数据库主机资源有空闲时,我们可以采用客户端多进程并行访问的方法来提高性能。如果数据库主机已经很忙时,采用多进程并行访问性能不会提高,反而可能会更慢。所以使用这种方式最好与DBA或系统管理员进行沟通后再决定是否采用。
例如:
我们有10000个产品ID,现在需要根据ID取出产品的详细信息,如果单线程访问,按每个IO要5ms计算,忽略主机CPU运算及网络传输时间,我们需要50s才能完成任务。如果采用5个并行访问,每个进程访问2000个ID,那么10s就有可能完成任务。
那是不是并行数越多越好呢,开1000个并行是否只要50ms就搞定,答案肯定是否定的,当并行数超过服务器主机资源的上限时性能就不会再提高,如果再增加反而会增加主机的进程间调度成本和进程冲突机率。
以下是一些如何设置并行数的基本建议:
如果瓶颈在服务器主机,但是主机还有空闲资源,那么最大并行数取主机CPU核数和主机提供数据服务的磁盘数两个参数中的最小值,同时要保证主机有资源做其它任务。
如果瓶颈在客户端处理,但是客户端还有空闲资源,那建议不要增加SQL的并行,而是用一个进程取回数据后在客户端起多个进程处理即可,进程数根据客户端CPU核数计算。
如果瓶颈在客户端网络,那建议做数据压缩或者增加多个客户端,采用map reduce的架构处理。
如果瓶颈在服务器网络,那需要增加服务器的网络带宽或者在服务端将数据压缩后再处理了。
数据库并行处理是指客户端一条SQL的请求,数据库内部自动分解成多个进程并行处理,如下图所示:
并不是所有的SQL都可以使用并行处理,一般只有对表或索引进行全部访问时才可以使用并行。数据库表默认是不打开并行访问,所以需要指定SQL并行的提示,如下所示:
select
/*+parallel(a,4)*/* from employee;
并行的优点:
使用多进程处理,充分利用数据库主机资源(CPU,IO),提高性能。
并行的缺点:
1、单个会话占用大量资源,影响其它会话,所以只适合在主机负载低时期使用;
2、只能采用直接IO访问,不能利用缓存数据,所以执行前会触发将脏缓存数据写入磁盘操作。
注:
1、并行处理在OLTP类系统中慎用,使用不当会导致一个会话把主机资源全部占用,而正常事务得不到及时响应,所以一般只是用于数据仓库平台。
2、一般对于百万级记录以下的小表采用并行访问性能并不能提高,反而可能会让性能更差。
用户评论
我去!遇到同样的问题了,查询的时候就报这个错误提示,折腾半天都没解决...Oracle Minus 语句写对了吗?
有16位网友表示赞同!
我也是在执行 ORACLE MINUS 命令的时候遇到了这个问题,后来发现是我的表的名称输入错误导致的报错;
有20位网友表示赞同!
别担心,遇到这种情况很正常!我前段时间也碰过相同的问题,查阅了 Oracle 的官方文档后发现是表不存在引发的报错。检查下你的 SQL 语句中使用的表名是否正确很重要!
有19位网友表示赞同!
碰到这个ORACLE MINUS 报错的时候千万不要急躁,首先要仔细检查一下所使用的MINUS命令格式和表的名称,然后再看看有没有其他可能导致错误的原因。
有19位网友表示赞同!
今天试着用Oracle Minus查询数据时也遇到了这个问题,好奇怪!查了一下oracle官方文档,说是因为表不存在造成的报错。我去把表创建好了,问题就解决了!
有6位网友表示赞同!
使用 Oracle Minus 时,要注意两边表的字段需要完全相同呀!如果不同导致结果错误的话也会报这个类型的错误信息;自己检查了一遍才发现这个问题,真坑人哦~
有10位网友表示赞同!
这也太郁闷了!好不容易写完了SQL语句,却因为表的不存在就报错!这种地方感觉Oracle真是太不人性化了,搞得人一头雾水。
有15位网友表示赞同!
我碰到过无数次这样的问题了,每次都让我头疼不已! ORACLE MINUS 的执行需要非常精确的表格数据和命名规范, 一少点错误 就容易报错
有14位网友表示赞同!
别急,遇到这个问题不用慌!仔细检查一下你的 SQL 语句中使用的表名是否正确,确保语句格式无误。如果还是不行,可以尝试查看 Oracle Error Logs 看看是否有更多信息提示解决问题的思路~
有19位网友表示赞同!
ORACLE MINUS确实好用,特别是用来比较两个数据集中的差异之处。但需要注意的是,如果你的数据量很大,这个命令执行可能会比较慢。有时候为了提高效率,可以考虑分批查询或者使用其他的方法。
有20位网友表示赞同!
这个报错我遇到过不止一次!每次都是不小心拼錯了表名导致的,真是要注意小细节啊~
有16位网友表示赞同!
oracle minus 语句真的很强大!可以让我想了解两个数据集之间的差异,轻松找到变化的地方。不过需要注意的是执行结果可能是比较复杂的,需要仔细分析。
有6位网友表示赞同!
使用ORACLE MINUS查询数据的时候,一定要注意两边数据的类型和格式要一致,否则会导致错误提示
有15位网友表示赞同!
Oracle Minus 命令挺实用的,可以用在很多场景中,比如找出两个数据库表之间的数据差异。但是需要注意的是,如果表字段过多,可能会导致报错,建议根据实际情况使用不同的方法。
有14位网友表示赞同!
我也遇到过这个ORACLE MINUS 报错,后来发现是数据类型不匹配造成的,要保证两个表的对应字段的数据类型一致才行...
有8位网友表示赞同!
Oracle的文档确实很详细,仔细阅读可以解决很多问题!不过新手学习的时候还是容易遇到一些坑啊,希望 Oracle 可以提供更直观的教程
有14位网友表示赞同!