-- 考虑备份数据库性能视图(最好是在新建的非生产使用的单独用户下操作,比如 TEST_USER 用户) -- 此外在数据库需要重启时,更应考虑备份这些视图 create table diag_session_&yyyymmdd_seq_area nologging as select * from gv$session; create table diag_session_wait_&yyyymmdd_seq_area nologging as select * from gv$session_wait; create table diag_process_&yyyymmdd_seq_area nologging as select * from gv$process; create table diag_sql_&yyyymmdd_seq_area nologging as select * from gv$sql; create table diag_sqlarea_&yyyymmdd_seq_area nologging as select * from gv$sqlarea; create table diag_sql_plan_&yyyymmdd_seq_area nologging as select * from gv$sql_plan; --耗性能 create table diag_lock_&yyyymmdd_seq_area nologging as select * from gv$lock; create table diag_locked_object_&yyyymmdd_seq_area nologging as select * from gv$locked_object; create table diag_access_&yyyymmdd_seq_area nologging as select * from gv$access; create table diag_latch_&yyyymmdd_seq_area nologging as select * from gv$latch; create table diag_latch_children_&yyyymmdd_seq_area nologging as select * from gv$latch_children; create table diag_Librarycache_&yyyymmdd_seq_area nologging as select * from gv_$Librarycache; create table diag_rowcache_&yyyymmdd_seq_area nologging as select * from gv_$rowcache; create table diag_sort_segment_&yyyymmdd_seq_area nologging as select * from gv$sort_segment; create table diag_sort_usage_&yyyymmdd_seq_area nologging as select * from gv$sort_usage; create table diag_log_history_&yyyymmdd_seq_area nologging as select * from gv$log_history; create table diag_log_&yyyymmdd_seq_area nologging as select * from gv$log; create table diag_logfile_&yyyymmdd_seq_area nologging as select * from gv$logfile; create table diag_transaction_&yyyymmdd_seq_area nologging as select * from gv$transaction; create table diag_parameter_&yyyymmdd_seq_area nologging as select * from gv$parameter; create table diag_session_longops_&yyyymmdd_seq_area nologging as select * from gv$session_longops; create table diag_bh_&yyyymmdd_seq_area nologging as select * from gv$bh; create table diag_filestat_&yyyymmdd_seq_area nologging as select * from gv$filestat; create table diag_segstat_&yyyymmdd_seq_area nologging as select * from gv$segstat; create table diag_tempstat_&yyyymmdd_seq_area nologging as select * from gv$tempstat; create table diag_datafile_&yyyymmdd_seq_area nologging as select * from gv$datafile; create table diag_tempfile_&yyyymmdd_seq_area nologging as select * from gv$tempfile; create table diag_open_cursors_&yyyymmdd_seq_area nologging as select * from gv$open_cursors;
-- 查谁占用了undo表空间 select r.name 回滚段名 , rssize / 1024 / 1024 / 1024 "rssize(g)" , s.sid , s.serial# , s.username 用户名 , s.status , s.sql_hash_value , s.sql_address , s.machine , s.module , substr(s.program, 1, 78) 操作程序 , r.usn , hwmsize / 1024 / 1024 / 1024 , shrinks , xacts from sys.v_$session S , sys.v_$transaction t , sys.v_$rollname r , v$rollstat rs where t.addr = s.taddr and t.xidusn = r.usn and r.usn = rs.usn order by rssize desc;
-- 查谁占用了temp表空间 select t.blocks * 16 / 1024 / 1024 , s.username , s.schemaname , t.tablespace , t.segtype , t.extents , s.program , s.osuser , s.terminal , s.sid , s.serial# from v$sort_usage t , v$session s where t.session_addr = s.saddr;
-- 还可查到具体SQL select sql.sql_id , t.blocks * 16 / 1024 / 1024 , s.username , s.schemaname , t.tablespace , t.segtype , t.extents , s.program , s.osuser , s.terminal , s.sid , s.serial# , sql.sql_text from v$sort_usage t , v$session s , v$sql sql where t.session_addr = s.saddr and t.sqladdr = sql.address and t.sqlhash = sql.hash_value;
动态局部
通过主机进程PID查SQL 通过主机进程PID查SQL(这个步骤和之前的top命令紧密相连,就是为了直接分析这些耗CPU的进程和哪些SQL有关系) 本来可以用如下方法来查,但是系统出现问题时,一般不容易查出来,太慢(有时用 ordered 或者 no_merge 的 HINT 有效,有时无效)
1 2 3 4 5 6 7 8 9 10
select /*+ ordered */ sql_text from v$sqltext a where (a.hash_value, a.address) in (select decode(sql_hash_value, 0, prev_hash_value, sql_hash_value) , decode(sql_hash_value, 0, prev_sql_addr, sql_address) from v$session b where b.paddr = (select addr from v$process c where c.spid = '&pid')) order by piece asc;
-- 等待事件(当前) select t.event, count(*) from v$session t group by event order by count(*) desc;
-- 等待事件(历史汇集) select t.event, t.total_waits from v$system_event t order by total_waits desc;
-- 游标使用情况 select inst_id, sid, count(*) from gv$open_cursor group by inst_id, sid having count(*) >= 1000 order by count(*) desc;
-- PGA占用最多的进程 select p.spid , p.pid , s.sid , s.serial# , s.status , p.pga_alloc_mem , s.username , s.osuser , s.program from v$process p , v$session s where s.paddr(+) = p.addr order by p.pga_alloc_mem desc;
-- 登录时间最长的SESSION(同时获取到 spid ,方便在主机层面 ps-ef|grep spid 来查看) select * from (select t.sid , t2.spid , t.PROGRAM , t.status , t.sql_id , t.PREV_SQL_ID , t.event , t.LOGON_TIME , trunc(sysdate - logon_time) from v$session t , v$process t2 where t.paddr = t2.ADDR and t.type <> 'BACKGROUND' order by logon_time) where rownum < 20;
-- 物理读和逻辑较多的5QL -- 逻辑读最多 select * from (select sql_id , sql_text , s.executions , s.last_load_time , s.first_load_time , s.disk_reads , s.buffer_gets from v$sql s where s.buffer_gets > 300 order by buffer_gets desc) where rownum <= 20;
-- 物理读最多 select * from (select sql_id , sql_text , s.executions , s.last_load_time , s.first_load_time , s.disk_reads , s.buffer_gets , s.parse_calls from v$sql s where s.disk_reads > 300 order by disk_reads desc) where rownum <= 20;
-- 执行次数最多 select * from (select sql_id , sql_text , s.executions , s.last_load_time , s.first_load_time , s.disk_reads , s.buffer_gets , s.parse_calls from v$sql s order by s.executions desc) where rownum <= 20;
-- 解析次数最多 select * from (select sql_id , sql_text , s.executions , s.last_load_time , s.first_load_time , s.disk_reads , s.buffer_gets , s.parse_calls from v$sql s order by s.parse_calls desc) where rownum < 20;
-- 求 DISK SORT 严重的 SQL select sess.username, sql.sql_text, sql.address, sort1.blocks from v$session sess , v$sqlarea sql , v$sort_usage sort1 where sess.serial# = sort1.session_num and sort1.sqladdr = sql.address and sort1.sqlhash = sql.hash_value and sort1.blocks > 200 order by sort1.blocks desc;
补充: 在 Oracle SQL 中,(+) 符号用于指示外连接(outer join)。除了 (+) 符号之外,Oracle 还支持使用 LEFT OUTER JOIN, RIGHT OUTER JOIN, 和 FULL OUTER JOIN 语法来表达外连接。
JOIN 的写法这里就省略了。下面说 (+) 的写法:
(+) 符号用于指示外连接,它可以出现在等式的一边或两边。
如果 (+) 出现在等式的左边,则表示 LEFT OUTER JOIN。
如果 (+) 出现在等式的右边,则表示 RIGHT OUTER JOIN。
如果 (+) 出现在等式的两边,则表示 FULL OUTER JOIN。
语法:
1 2
FROM left_table, right_table WHERE left_table.column (+) = right_table.column
语法差异:
使用 LEFT OUTER JOIN, RIGHT OUTER JOIN, 和 FULL OUTER JOIN 语法时,可以使用现代的 ANSI SQL 标准语法。
使用 (+) 符号时,需要使用旧式的逗号连接语法,并在 WHERE 子句中指定外连接条件。
性能:
在大多数情况下,使用现代的外连接语法 (LEFT OUTER JOIN, RIGHT OUTER JOIN, 和 FULL OUTER JOIN) 可能会产生更好的执行计划和性能。
-- 捕获出需要使用绑定变量的SQL(这里只能适配大多数语句) Drop table t1 purge; create table t1 as select sql_text, module from v$sqlarea; alter table t1 add sql_text_wo_constants varchar2(1000);
CREATE OR REPLACE FUNCTION remove_constants(p_query IN VARCHAR2) RETURN VARCHAR2 AS l_query LONG; l_char VARCHAR2(10); l_in_quotes BOOLEAN DEFAULT FALSE; BEGIN FOR i IN 1..LENGTH(p_query) LOOP l_char := SUBSTR(p_query, i, 1); IF (l_char = '''') THEN IF l_in_quotes THEN l_in_quotes := FALSE; ELSE l_in_quotes := TRUE; l_query := l_query || '''#'; END IF; ELSIF NOT l_in_quotes THEN l_query := l_query || l_char; END IF; END LOOP;
-- 移除多余的 @ 符号 FOR i IN 0..8 LOOP l_query := REPLACE(l_query, LPAD('@', 10 - i, '@'), '@'); l_query := REPLACE(l_query, LPAD('', 10 - i, ''), ''); END LOOP;
RETURN UPPER(l_query); END; -- 编译函数 ALTER FUNCTION TEST_USER.remove_constants COMPILE; update TEST_USER.t1 set sql_text_wo_constants = remove_constants(sql_text); commit;
-- 执行完上述动作后,以下SQL语句可以完成未绑定变量语句的统计 select sql_text_wo_constants, module, count(*) from t1 group by sql_text_wo_constants, module having count(*) > 100 order by 3 desc;
-- 检查数据库表和索引是否存在并行度设在其中的情况(很多时候有人用parallel建了表或索引,忘记alter table xxx noparallel关闭了)。 select t.owner, t.table_name, degree from dba_tables t where t.degree > '1'; select t.owner, t.table_name, index_name, degree, status from dba_indexes t where owner in ('TEST_USER') and t.degree > '1'; -- 有问题就要处理,比如索引有并行,就处理如下: select 'alter index ' || t.owner || '.' || index_name || 'noparallel;' from dba_indexes t where owner in ('TEST_USER') and t.degree > '1';
select table_name, blocks, num_rows from user_tables where blocks / num_rows >= 0.2 and num_rows is not null and num_rows <> 0 and blocks >= 10000; -- 这个就可以预测到哪些是高水平位没释放的表。 -- 其中blocks>:=10000是因为低于10000的块说明表的体积太小了,释放或不释放无所谓。
-- 附(可以释放高水平位的脚本,在 Oracle 的 shrink 方法无效时可采纳): create or replace package pkg_shrink Authid Current_User as /* 功能:将delete后的表降低高水平 */ procedure p_move_tab(p_tab varchar2); procedure p_cal_bytes(p_status varchar2, p_tab varchar2); procedure p_rebuid_idx(p_tab varchar2); procedure p_main(p_table_name varchar2); end pkg_shrink; create or replace package body pkg_shrink as v_sql varchar2(4000); procedure p_cal_bytes(p_status varchar2, p_tab varchar2) as v_tab_bytes number; v_idx_bytes number; v_str_tab varchar2(4000); v_str_idx varchar2(4000); begin select sum(bytes) / 1024 / 1024 into v_tab_bytes from user_segments where segment_name = upper(p_tab); select sum(bytes) / 1024 / 1024 into v_idx_bytes from user_segments where segment_name IN (SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME = upper(p_tab)); v_str_tab := p_status || '表' || p_tab || '的大小为' || v_tab_bytes || 'M'; if v_idx_bytes is null then v_str_idx := p_status || '无索引'; else v_str_idx := p_status || '索引的大小为' || v_idx_bytes || 'M'; end if; dbms_output.put_line(v_str_tab || ';' || v_str_idx); end p_cal_bytes;
procedure p_move_tab(p_tab varchar2) as V_IF_PART_TAB NUMBER; begin SELECT COUNT(*) INTO V_IF_PART_TAB FROM user_part_tables WHERE TABLE_NAME = upper(P_TAB); IF V_IF_PART_TAB = 0 THEN --非分区表 v_sql := 'alter table ' || p_tab || ' move'; -- 完成表的MOVE动作,从而做到降低高水平位,不过也带来了索引的失效! DBMS_OUTPUT.put_line(v_sql); execute immediate v_sql; ELSE -- 分区表 for i in (SELECT * from USER_TAB_PARTITIONS WHERE TABLE_NAME = upper(p_tab)) loop v_sql := 'alter table ' || p_tab || ' move partition ' || i.partition_name; -- 完成分区表的MOVE动作,同样带来了索引失效! DBMS_OUTPUT.put_line(v_sql); execute immediate v_Sql; end loop; END IF; end p_move_tab;
procedure p_rebuid_idx(p_tab varchar2) as V_NORMAL_IDX NUMBER; V_PART_IDX NUMBER; begin SELECT COUNT(*) INTO V_NORMAL_IDX FROM user_indexes where table_name = 'PART_TAB' AND INDEX_NAME NOT IN (SELECT INDEX_NAME FROM user_part_indexes); IF V_NORMAL_IDX >= 1 THEN -- 普通索引 for i in (select * from user_indexes where table_name = upper(p_tab) AND INDEX_NAME NOT IN (SELECT INDEX_NAME FROM user_part_indexes)) loop v_sql := 'alter index ' || i.index_name || ' rebuild'; -- 将失效的普通索引重建 DBMS_OUTPUT.put_line(v_sql); execute immediate v_sql; end loop; END IF; SELECT COUNT(*) INTO V_PART_IDX FROM user_part_indexes WHERE TABLE_NAME = 'PART_TAB'; IF V_PART_IDX >= 1 THEN -- 分区索引 for i in (SELECT * from User_Ind_Partitions WHERE index_name in (select index_name from user_part_indexes where table_name = upper(p_tab))) loop v_sql := 'alter index ' || i.index_name || ' rebuild partition ' || i.partition_name; -- 将失效分区索引重建 DBMS_OUTPUT.put_line(v_sql); execute immediate v_Sql; end loop; END IF; end p_rebuid_idx;
procedure p_main(p_table_name varchar2) as begin for i in (select * from (SELECT SUBSTR(s, INSTR(s, ',', 1, ROWNUM) + 1, INSTR(s, ',', 1, ROWNUM + 1) - INSTR(s, ',', 1, ROWNUM) - 1) AS TYPE_ID FROM (SELECT ',' || p_table_name || ',' AS s FROM DUAL) CONNECT BY ROWNUM <= 100) WHERE type_id IS NOT NULL ) loop -- 在外面SELECT再套一层是必须的,否则只会循环一次。另外type_id IS NOT NULL是必须的,否则会多循环 DBMS_OUTPUT.put_line('当前处理的表为' || i.TYPE_ID); p_cal_bytes('未降低高水平位前', i.type_id); p_move_tab(i.type_id); p_rebuid_idx(I.TYPE_ID); p_cal_bytes('降低高水平位后', i.type_id); end loop; end p_main; end pkg_shrink;
-- 编译 alter package PKG_SHRINK compile reuse settings
-- 检查哪些对象的统计信息不够新,或者从未统计过(注意,让未统计过的在前面,即nulls first)。 -- 检查统计信息是否被收集 select t.JOB_NAME, t.PROGRAM_NAME, t.state, t.enabled from dba_scheduler_jobs t where job_name = 'GATHER_STATS_JOB';
-- 检查哪些未被收集或者很久没收集 select owner , table_name , t.last_analyzed , t.num_rows , t.blocks , t.object_type from dba_tab_statistics t where owner in ('TEST_USER') and (t.last_analyzed is null or t.last_analyzed < sysdate - 14) order by t.last_analyzed nulls first;
-- 查看数量 select count(*) from dba_tab_statistics t where owner in ('TEST_USER') and (t.last_analyzed is null or t.last_analyzed < sysdate - 14);
全局临时表情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-- 检查全局临时表有没有被收集统计信息 select owner , table_name , t.last_analyzed , t.num_rows , t.blocks from dba_tables t where t.temporary = 'Y' and owner in ('TEST_USER');
-- 用户的权限情况 select * from dba_role_privs where grantee = 'TEST_USER';
-- 最大的前20个对象(然后再进一步COUNT(*)统计其记录数) select * from (select owner , segment_name , segment_type , sum(bytes) / 1024 / 1024 / 1024 object_size from DBA_segments WHERE owner in ('TEST_USER') group by owner, segment_name, segment_type order by object_size desc) where rownum < 50;
-- 表空间使用情况 select a.tablespace_name "表空间名" , a.total_space "总空间(g)" , nvl(b.free_space, 0) "剩余空间(g)" , a.total_space - nvl(b.free_space, 0) "使用空间(g)" , trunc(nvl(b.free_space, 0) / a.total_space * 100, 2) "剩余百分比%" from (select tablespace_name , trunc(sum(bytes) / 1024 / 1024 / 1024, 2) total_space from dba_data_files group by tablespace_name) a , (select tablespace_name , trunc(sum(bytes / 1024 / 1024 / 1024), 2) free_space from dba_free_space group by tablespace_name) b where a.tablespace_name = b.tablespace_name(+) order by 5;
-- 整个用户有多大(比如 TEST_USER 用户) select sum(bytes) / 1024 / 1024 / 1024 "G" from dba_segments where owner = 'TEST_USER';
-- 回收站情况 select SUM(BYTES) / 1024 / 1024 / 1024 from DBA_SEGMENTS WHERE owner = 'TEST_USER' AND SEGMENT_NAME LIKE 'BINS%';
-- 分区最多的前20个对象(先知道表就可以大概了解了,索引可以后续再观察) select * from (select table_owner, table_name, count(*) cnt from dba_tab_partitions WHERE table_owner in ('TEST_USER') group by table_owner, table_name order by cnt desc) where rownum < 20;
静态局部
检查有哪些函数索引或者位图索引
1 2 3 4 5 6 7 8 9 10
-- 检查有哪些函数索引或者位图索引(大多数情况下开发人员对这两类索引是使用不当的,所以需要捞出来确认一下) select t.owner , t.index_name , t.index_type , t.status , t.blevel , t.leaf_blocks from dba_indexes t where index_type in ('BITMAP', 'FUNCTION-BASED NORMAL') and owner in ('TEST_USER');
检查CACHE小于20的序列
1 2 3 4 5 6 7
-- 检查序CACHE小于20的序列的情况(一般情况下可将其增至1000左右,序列默认的20太小) select t.sequence_name , t.cache_size , 'alter sequence ' || t.sequence_owner || '.' || t.sequence_name || ' cache 1000;' from dba_sequences t where sequence_owner in ('TEST_USER') AND CACHE_SIZE < 20;
分析需要跟踪的表和索引的情况
查看表大小情况
1 2 3 4 5 6 7 8 9
-- 记录的大小 select count(*) from TANBLE_NAME;
-- 物理的大小 select segment_name, sum(bytes) / 1024 / 1024 from user_segments where segment_name in ('TANBLE_NAME') group by segment_name;
-- 查看表信息 select t.table_name , t.num_rows , t.blocks -- t.empty_blocks, --统计信息不收集这个字段,所以不需要这个字段了 , t.degree , t.last_analyzed , t.temporary , t.partitioned , t.pct_free , t.tablespace_name from user_tables t where table_name in ('TABLE_NAME');
-- 查看分区表相关信息 -- 查看分区表相关信息(user_part_tables记录分区的表的信息,user_tab_partitions记录表的分区的信息) -- 以下了解这些表的分区是什么类型的,有多少个分区 select t.table_name , t.partitioning_type , t.partition_count from user_part_tables t where table_name in ('TABLE_NAME');
-- 以下了解这些表以什么列作为分区 Select name , object_type , column_name from user_part_key_columns where name in ('TABLE_NAME');
-- 以下了解这些表的分区范围是多少 SELECT table_name, partition_name, high_value, tablespace_name FROM user_tab_partitions t where table_name in ('TABLE_NAME') order by table_name, t.partition_position;
select t2.table_name , t1.segment_name , sum(t1.bytes) / 1024 / 1024 from user_segments t1 , user_indexes t2 where t1.segment_name = t2.index_name and t1.segment_type like '%INDEX%' and t2.table_name in ('T1') group by t2.table_name, t1.segment_name order by table_name;
-- 结构情况(高度、重复度、并行度、叶子高度、聚合因子、记录数、状态、最近分析时间...) select t.table_name , t.index_name , t.num_rows , t.index_type , t.status , t.clustering_factor , t.blevel , t.distinct_keys , t.leaf_blocks , t.uniqueness , t.degree , t.last_analyzed from user_indexes t where table_name in ('TABLE_NAME');
查看索引列信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
-- 以下可以查出来的是索引的列是什么(无论分区表和非分区表都可以查出) select t.table_name, t.index_name, t.column_name, t.column_position, t.DESCEND from user_ind_columns t where table_name in ('TABLE_NAME') order by table_name, index_name, column_position;
-- 以下查出的都是分区索引 select table_name, index_name, partitioning_type, partition_count from user_part_indexes where table_name in ('TABLE_NAME') order by table_name, index_name;
select index_name, partition_name, status, blevel, leaf_blocks from user_ind_partitions where index_name in (select index_name from user_indexes where table_name in ('TABLE_NAME'));
开发规范 让开发者驾轻就熟
sql 编写规范
单条SQL长度不宜超过100行
1 2 3 4 5 6
-- 判断过长 sql select sql_id, count(*) from v$sqltext group by sql_id having count(*) >= 100 order by count(*) desc;
drop table t purge; create table t as select * from v$sql_plan;
-- 使用Nested Loops Join但是未用到索引的,比较可疑 select * from t where sql_id not in (select sql_id from t where sql_id in (select sql_id from t where operation = 'NESTED LOOPS') and (operation like '%INDEX%' or object_owner like '%SYS%')) and sql_id in (select sql_id from t where operation = 'NESTED LOOPS');
尽量避免HNT在代码中出现
1 2 3 4 5 6 7 8 9 10 11 12
-- 找出非SYS用户用HINT的所有SQL来分析 select sql_text , sql_id , module , t.service , first_load_time , last_load_time , executions , service from v$sql t where sql_text like '%/*+%' and t.SERVICE not like 'SYS$%';
同一SQL模块避免出现大量相似之处 这种SQL写法一般比较可疑,一般可以优化,比如 WITH 子句等等,所以出现后需引起注意。
用到并行度需谨慎
表和索引属性设置并行 找出被设置成并行属性的表和索引,并修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14
select t.owner, t.table_name, degree from dba_tables t where t.degree > '1';
select t.owner, t.table_name, index_name, degree, status from dba_indexes t where owner in ('TEST_USER') and t.degree > '1';
-- 有问题就要处理,比如索引有并行,就处理如下: select 'alter index ' || t.owner || '.' || index_name || ' noparallel;' from dba_indexes t where owner in ('TEST_USER') and t.degree > '1';
SQL中 HINT 的并行设置 属性未设并行,但是 HINT 设并行的SQL
1 2 3 4 5 6 7 8 9 10
select sql_text , sql_id , module , service , first_load_time , last_load_time , executions from v$sql t where sql_text like '%parall%' and t.SERVICE not like 'SYS$%';
尽量避免对列进行运算 捞取对列进行运算的SQL
1 2 3 4 5 6 7 8 9 10 11 12
select sql_text , sql_id , module , t.service , first_load_time , last_load_time , executions from v$sql t where (upper(sql_text) like '%TRUNC%' or upper(sql_text) like '%TO_DATE%' or upper(sql_text) like '%SUBSTR%') and t.SERVICE not like 'SYS$%';
select * from (select name , t.type , sum(case when text like '%--%' then 1 else 0 end) / count(*) rate from user_source t where type in ('package body', 'procedure', 'function') -- 包头就算了 group by name, type having sum(case when text like '%-%' then 1 else 0 end) / count(*) <= 1 / 10) order by rate;
代码必须提供最小化测试案例于注释中 这是一个值得推崇的好习惯,对新人接手熟悉程序尤为有用。
绑定变量
相似语句需考虑绑定变量 这里就不提供脚本了,在前面的“检查系统使用绑定变量的情况”中已提供代码。
动态SQL最容易遗忘绑定变量 一般来说,动态SQL未用绑定变量的情况多半是因为未使用 USING 关键字,所以可用如下脚本来搜索可疑的未用绑定变量的动态SQL。 动态SQL未用 USING 有可能未用绑定变量
1 2 3 4 5 6 7 8 9 10
select * from user_source where name in (select name from user_source where name in (select name from user_source where UPPER(text) like '%EXECUTE IMMEDIATE%')) and name in (select name from user_source where name in (select name from user_source where UPPER(text) like '%||%')) and name not in (select name from user_source where name in (select name from user_source where upper(text) not like '%USING%'));
select t1.sid, t1.value, t2.name from v$sesstat t1 , v$statname t2 --where t2.name like '%commit%' where t2.name like '%user commits%' --可以只选user commits,其他系统级的先不关心 and t1.STATISTIC# = t2.STATISTIC# and value > 10000 order by value desc;
-- 表大小超过10GB未建分区的 select owner , segment_name , segment_type , sum(bytes) / 1024 / 1024 / 1024 object_size from dba_segments WHERE segment_type = 'TABLE' -- 此处说明是普通表,不是分区表,如果是分区表,类型是TABLE PARTITION group by owner, segment_name, segment_type having sum(bytes) / 1024 / 1024 / 1024 >= 10 order by object_size desc;
-- 分区个数超过100个的表 select table_owner, table_name, count(*) cnt from dba_tab_partitions WHERE table_owner in ('TEST_USER') having count(*) >= 100 group by table_owner, table_name order by cnt desc;
-- 表大小超过10GB,有时间字段,可以考虑在该列上建立分区 -- 超过10GB的大表没有时间字段 select T1.*, t2.column_name, t2.data_type from (select segment_name , segment_type , sum(bytes) / 1024 / 1024 / 1024 object_size from user_segments WHERE segment_type = 'TABLE' --此处说明是普通表,不是分区表,如果是分区表,类型是TABLE PARTITION group by segment_name, segment_type having sum(bytes) / 1024 / 1024 / 1024 >= 0.01 order by object_size desc) t1 , user_tab_columns t2 where t1.segment_name = t2.table_name(+) and t2.DATA_TYPE = 'DATE' -- 来说明这个大表有时间列
-- 上述语句和下面的语句进行观察比较 下面只是过滤了大小 select segment_name , segment_type , sum(bytes) / 1024 / 1024 / 1024 object_size from user_segments WHERE segment_type = 'TABLE' -- 此处说明是普通表,不是分区表,如果是分区表,类型是TABLE PARTITION group by segment_name, segment_type having sum(bytes) / 1024 / 1024 / 1024 >= 0.01 order by object_size desc;
-- 找出有建触发器的表,同时观察该表多大 select trigger_name, table_name, tab_size from user_triggers t1 , (select segment_name, sum(bytes / 1024 / 1024 / 1024) tab_size from user_segments t where t.segment_type = 'TABLE' group by segment_name) t2 where t1.TABLE_NAME = t2.segment_name;
-- 查询哪些索引是函数索引 select t.index_name , t.index_type , t.status , t.blevel , t.leaf_blocks from user_indexes t where index_type in ('FUNCTION-BASED NORMAL');
位图索引遇到更新将是噩梦,需谨慎设计
位图索引不适合用在表频繁更新的场合。
位图索引不适合在所在列重复度很低的场合。
因为位图索引的应用比较特殊,适用场合比较少,因此有必要捞取出系统中的位图索引,进行核对检测。
1 2 3 4 5 6 7 8
-- 查询哪些索引是位图索引 select t.index_name , t.index_type , t.status , t.blevel , t.leaf_blocks from user_indexes t where index_type in ('BITMAP');
范围查询影响组合索引 组合查询中,如果有等值条件和范围条件组合的情况,等值条件在前,性能更高。 如:where col1=2 and col2>=100 and col2<=120,此时是col1,col2的组合索引性能高过col2,col1的组合索引。
1 2 3 4 5 6 7 8 9 10 11
-- 将有不等值查询的SQL捞取出来分析 select sql_text , sql_id , service , module , t.first_load_time , t.last_load_time from v$sql t where (sql_text like '%>%' or sql_text like '%<%' or sql_text like '%<>%') and sql_text not like '%=>%' and service not like 'SYS$%';
-- 捞取超过4个字段组合的联合索引 select table_name, index_name, count(*) from user_ind_columns group by table_name, index_name having count(*) >= 4 order by count(*) desc;
-- 查询表的前缀是否以t_开头 select* from user_tables where substr(table_name, 1, 2) <> 'T_';
-- 查询视图的前缀是否以v_开头 select view_name from user_views where substr(view_name, 1, 2) <> 'V_';
-- 查询同义词的前缀是否以s_开头 select synonym_name, table_owner, table_name from user_synonyms where substr(synonym_name, 1, 2) <> 'S_';
-- 查询簇表的前缀是否以c_开头 select t.cluster_name, t.cluster_type from user_clusters t where substr(cluster_name, 1, 2) <> 'C_';
-- 查询序列的前缀是否以seq开头或结尾 select sequence_name, cache_size from user_sequences where sequence_name not like '%SEQ%';
-- 查询存储过程是否以p_开头 select object_name, procedure_name from user_procedures where object_type = 'PROCEDURE' and substr(object_name, 1, 2) <> 'P_';
-- 查询函数是否以f_开头 select object_name, procedure_name from user_procedures where object_type = 'FUNCTION' and substr(object_name, 1, 2) <> 'F_';
-- 查询包是否以pkg开头 select object_name, procedure_name from user_procedures where object_type = 'PACKAGE' and substr(object_name, 1, 4) <> 'PKG';
-- 查询类是否以typ开头 select object_name, procedure_name from user_procedures where object_type = 'TYPE' and substr(object_name, 1, 4) <> 'TYP';
-- 查询主键是否以pk_开头 select constraint_name, table_name from user_constraints where constraint_type = 'p' and substr(constraint_name, 1, 3) <> 'PK_' and constraint_name not like 'BINS%';
-- 查询外键是否以fk_开头 select constraint_name, table_name from user_constraints where constraint_type = 'R' and substr(constraint_name, 1, 3) <> 'FK_' and constraint_name not like 'BINS%';
-- 查询唯一索引是否以ux_开头 select constraint_name, table_name from user_constraints where constraint_type = 'U' and substr(constraint_name, 1, 3) <> 'UX_' and table_name not like 'BINS%';
-- 查询普通索引是否以idx_开头 select index_name, table_name from user_indexes where index_type = 'NORMAL' and uniqueness = 'NONUNIQUE' and substr(index_name, 1, 4) <> 'IDX_' and table_name not like 'BINS%';
-- 查询位图索引是否以bx_开头 select index_name, table_name from user_indexes where index_type LIKE '%BIT%' and substr(index_name, 1, 3) <> 'BX_' and table_name not like 'BINS%';
-- 查询函数索引是否以fx_开头 select index_name, table_name from user_indexes where index_type = 'FUNCTION-BASED NORMAL' and substr(index_name, 1, 3) <> 'FX_' and table_name not like 'BINS%';