简介面向Oracle数据库学习者和项目实战入门者的基金交易平台数据库设计文档围绕招商银行某分行开放式基金交易平台的业务需求展开重点梳理基金公司、基金、活期账户、理财账户、基金账户及交易等核心数据表的设计思路。内容从后台基金管理、客户账户管理、交易审核到前台查询与委托交易逐步分析并给出主要字段、数据类型、主外键关联及状态约束适合用于课程设计、数据库建模练习或项目答辩参考。压缩包内共1个doc文件约245KB篇幅集中、便于查阅。已有453人学习可作为从需求分析到表结构落地的实战样例对理解金融类业务系统表间关系、外键设计与状态字段约束有直接帮助。1. 从一次上线前的慢查询说起oracle项目实战到底在解决什么问题压测跑完第三轮一条跑了两年、平时 30 毫秒的订单查询突然变成 12 秒应用的连接池在五分钟内被打满。这是 oracle项目实战 里最常见的开场不是不会写 SQL而是不知道数据库在什么条件下会换一条路走。这类问题的根子往往不在代码而在实例参数、表设计、统计信息和执行计划之间的连锁反应。oracle项目实战 讲的是把一套业务系统真正落到 Oracle 数据库上的全过程环境怎么起、账号怎么分、表怎么建、索引怎么给、批量逻辑放不放进 PL/SQL、出问题看哪几个视图。它解决的不是“能不能跑”而是“跑了半年之后还稳不稳”。适合正在做数据库迁移、老系统改造或者接手了一个没人敢动的存量库的工程师也适合写了几年 CRUD、但一直没系统看过执行计划的人。后面按六个环节推进先把实例和账号立住再谈表结构与执行计划然后是 PL/SQL 与批处理的边界接着是五条踩过的坑最后收在备份与计划固化上。2. 环境与账号把 oracle项目实战 的第一公里跑通很多人对 Oracle 的第一印象是“装不上”。真机装、图形界面装、配置监听、建库一套下来半天没了而且换台机器又要重来一遍。做 oracle项目实战第一步应该是把环境变成可复现的一条命令起来一条命令删掉数据目录挂出来。这样后面所有关于参数、执行计划、统计信息的实验才有意义——你总不能在一个连内存都不够的实例上判断一条 SQL 该不该走索引。2.1 用容器起一个能反复推倒重来的实例常见做法是用官方提供的镜像起单实例把数据文件目录挂到宿主机上容器删了数据还在。# 拉镜像标签按你所在环境的镜像仓库实际可用的为准 docker pull oracle/database:19.3.0-ee # 起实例1521 是监听端口5500 是 EM Express docker run -d \ --name ora-lab \ --shm-size2g \ -p 1521:1521 -p 5500:5500 \ -e ORACLE_SIDORCLCDB \ -e ORACLE_PDBORCLPDB1 \ -e ORACLE_PWDLab#2024 \ -e INIT_SGA_SIZE2048 \ -e INIT_PGA_SIZE512 \ -v /data/oracle/oradata:/opt/oracle/oradata \ oracle/database:19.3.0-ee # 首次启动要建库盯着日志等这一行 docker logs -f ora-lab | grep -i DATABASE IS READY TO USEORACLE_SID是实例名ORACLE_PDB是可插拔数据库名业务连接串里填的是后者//127.0.0.1:1521/ORCLPDB1。INIT_SGA_SIZE和INIT_PGA_SIZE单位是 MB加起来不要超过容器可用内存的七成否则建库阶段就会因为分配共享内存失败而反复重启日志里只有一行ORA-27102: out of memory。--shm-size必须给/dev/shm默认 64MBSGA 稍微大一点就起不来。如果你手上是已经有实例的环境跳过这一步但要记下两件事SELECT * FROM v$version;确认版本SHOW PARAMETER sga_target确认内存。后面调优时所有的判断都建立在这两个值上。顺手把v$parameter里optimizer_mode、cursor_sharing、db_block_size三个值抄下来它们会直接影响执行计划的样子。连上去docker exec -it ora-lab sqlplus / as sysdba-- 切到 PDB业务对象都建在这里 ALTER SESSION SET CONTAINER ORCLPDB1; SHOW CON_NAME;这一步翻车的点在于很多人sqlplus / as sysdba连进去就建表结果建在了 CDB 的 SYSTEM 表空间里后面查USER_TABLES查不到还以为建表失败。养成习惯每条会话开始先SHOW CON_NAME看一眼。2.2 表空间、用户与权限业务账号不该拿到 SYS用 SYS 或 SYSTEM 跑业务是 oracle项目实战 里最不该有的捷径。三个理由一是 SYSTEM 表空间被业务数据撑满之后数据字典跟着写不进去库直接进不去二是审计日志里所有操作都是 SYSTEM出问题追不到人三是 SYS 的权限太大一条误写的DROP USER ... CASCADE就能把整个 PDB 清空没有后悔药。正确的拆法是一个业务表空间、一个临时表空间、一个业务账号用角色收口权限。-- 以 SYSDBA 登录后切到 PDB ALTER SESSION SET CONTAINER ORCLPDB1; -- 业务数据表空间本地管理 自动段空间管理 CREATE TABLESPACE APP_DATA DATAFILE /opt/oracle/oradata/ORCLCDB/ORCLPDB1/app_data01.dbf SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 32G EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO; -- 临时表空间排序、哈希连接溢出都落在这里 CREATE TEMPORARY TABLESPACE APP_TEMP TEMPFILE /opt/oracle/oradata/ORCLCDB/ORCLPDB1/app_temp01.dbf SIZE 512M AUTOEXTEND ON NEXT 128M MAXSIZE 8G; -- 业务账号配额只给业务表空间 CREATE USER app_user IDENTIFIED BY App#2024 DEFAULT TABLESPACE APP_DATA TEMPORARY TABLESPACE APP_TEMP QUOTA UNLIMITED ON APP_DATA PROFILE DEFAULT ACCOUNT UNLOCK; -- 用角色收口别一个个 GRANT 给用户 CREATE ROLE app_rw; GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE, CREATE TRIGGER, CREATE VIEW TO app_rw; GRANT app_rw TO app_user;关键参数的意思AUTOEXTEND ON NEXT 256M让数据文件每次涨 256MB比一次涨 10MB 少很多次物理扩展也不会像NEXT 4G那样一次吃满磁盘MAXSIZE一定要写不写就是无限增长磁盘写满时数据库会直接挂起比报错更难排查QUOTA UNLIMITED是用户级配额不给的话建表会报ORA-01950: no privileges on tablespace。EXTENT MANAGEMENT LOCAL AUTOALLOCATE和SEGMENT SPACE MANAGEMENT AUTO这两个是在建表空间时唯一值得硬记的选项。前者让区段管理交给位图后者让段内的空闲块交给数据库自己判断能避开大量手工调pctfree的麻烦。老系统里常见的DICTIONARY和MANUAL是上世纪的默认值迁移时如果看到说明这个库有年头了顺手改掉。建完之后做一次自检-- 确认账号落在正确的表空间 SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username APP_USER; -- 确认表空间用量别等写满了才发现 SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024/1024, 2) AS size_gb FROM dba_data_files GROUP BY tablespace_name;这里有个容易被忽略的细节临时表空间不归dba_data_files管它在dba_temp_files里。查临时空间用量要用v$temp_space_header或者dba_temp_free_space。做 oracle项目实战 时遇到过好几次“表空间明明还有空间却报 ORA-01652 无法扩展临时段”就是查错了视图。3. 表设计与执行计划oracle项目实战 里最花钱的两个决定一个 Oracle 项目上线之后还能改的东西很多——参数可以调、索引可以加、SQL 可以重写。唯独表结构改起来成本最高涉及数据搬迁、应用改造、灰度切换。所以建模这一步值得多花两天想清楚。而执行计划是表设计质量的唯一客观检验你以为合理的索引优化器可能一条都不用。3.1 从业务字段到表结构类型、约束与分区怎么定先看一张订单表的标准写法CREATE TABLE t_order ( order_id NUMBER(19) NOT NULL, order_no VARCHAR2(32 CHAR) NOT NULL, cust_id NUMBER(19) NOT NULL, order_status VARCHAR2(8 CHAR) DEFAULT NEW NOT NULL, amount NUMBER(18,2) DEFAULT 0 NOT NULL, created_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL, remark VARCHAR2(500 CHAR), CONSTRAINT pk_t_order PRIMARY KEY (order_id) USING INDEX LOCAL, CONSTRAINT uk_t_order_no UNIQUE (order_no), CONSTRAINT ck_t_order_amt CHECK (amount 0) ) PARTITION BY RANGE (created_at) INTERVAL (NUMTOYMINTERVAL(1, MONTH)) ( PARTITION p_init VALUES LESS THAN (TIMESTAMP 2024-01-01 00:00:00) );几个必须解释的选择。VARCHAR2(32 CHAR)里的CHAR不能省默认是BYTE在 UTF-8 字符集下一个汉字占 3 个字节VARCHAR2(32)实际只能存 10 个汉字左右超了就是ORA-12899: value too large for column。这个错在测试环境纯英文数据永远不会出现一上生产就批量报。NUMBER(18,2)存金额别用BINARY_FLOAT或BINARY_DOUBLE。二进制浮点在十进制小数的表示上本身就带误差做SUM聚合时会累积对账时差几分钱查起来极其痛苦。NUMBER是十进制存储没有这个问题。TIMESTAMP(6)而不是DATE。DATE只精确到秒同一个秒内下单的多条记录按时间排序是随机的分页查询会出现记录重复或丢失。TIMESTAMP(6)精确到微秒配合SYSTIMESTAMP默认值能保证顺序稳定。分区用INTERVAL自动分区比手写RANGE好在你不用每个月提前建分区。插入数据落在哪个区间数据库自己建对应分区。NUMTOYMINTERVAL(1, MONTH)表示按月。分区的好处不只是查询裁剪更关键是清理历史数据时用ALTER TABLE ... DROP PARTITION秒级完成而DELETE FROM ... WHERE created_at ...可能要跑几个小时还会产生海量 undo。主键上用LOCAL索引意思是每个分区自己维护一棵索引树。好处是分区维护drop / truncate partition不影响其他分区的索引坏处是如果查询条件不带分区键就要扫所有分区的索引。经验判断如果order_id是全局唯一且查询几乎总是按主键来用GLOBAL索引更省事如果查询大量按时间范围走LOCAL更合适。不确定就先LOCAL后面真的慢了再重建。再加一个复合索引CREATE INDEX idx_t_order_cust_time ON t_order (cust_id, created_at) LOCAL;复合索引的前导列是cust_id所以下面这条查询能命中SELECT order_id, amount FROM t_order WHERE cust_id 10086 AND created_at TIMESTAMP 2024-06-01 00:00:00;但如果反过来只查created_at这个索引就用不上因为前导列没出现在谓词里。这就是 oracle项目实战 里最常被问到的问题索引建了但没生效八成交在列顺序上。原则是等值条件放前面范围条件放后面选择性高的列优先。还有一个容易忽略的created_at上如果已经有分区裁剪再单独建一个created_at的索引通常是浪费。每次插入都要维护额外的索引树写性能白白下降。做表设计时列一张“查询场景 → 谓词 → 索引”的对照表比拍脑袋加索引靠谱得多。3.2 执行计划怎么读从 EXPLAIN PLAN 到真实行数EXPLAIN PLAN是把 SQL 交给优化器让它生成计划但不执行。适合快速看访问路径EXPLAIN PLAN FOR SELECT order_id, amount FROM t_order WHERE cust_id 10086 AND created_at TIMESTAMP 2024-06-01 00:00:00; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, TYPICAL));输出里要盯三样东西TABLE ACCESS后面是FULL还是BYINDEX RANGE SCAN之类Cost和Cardinality预估行数还有缩进层级反映的连接顺序。但EXPLAIN PLAN有个坑它不做绑定变量窥探。如果你的 SQL 在应用里是带绑定变量的真实计划可能和这里看到的不一样。要看真的得从游标缓存里取-- 先让语句真实执行一次加上收集统计的提示 SELECT /* gather_plan_statistics */ order_id, amount FROM t_order WHERE cust_id 10086 AND created_at TIMESTAMP 2024-06-01 00:00:00; -- 再从游标缓存取出实际执行统计 SELECT * FROM TABLE( DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, ALLSTATS LAST) );ALLSTATS LAST会把每一步的E-Rows预估行数和A-Rows实际行数并排打出来。这两个值差一个数量级以上基本就找到了问题根源优化器估错了行数于是选错了连接方式或者访问路径。举个真实的翻车场景一张订单表的分区键是created_at某次批量导入之后统计信息没更新优化器认为整个表只有 2000 行于是对一个实际 800 万行的表选了全表扫描配合NESTED LOOPS跑了两小时。日志里什么错都没有就是慢没有黑匣子根本看不出来。统计信息的手工收集BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname APP_USER, tabname T_ORDER, cascade TRUE, degree 4, method_opt FOR ALL COLUMNS SIZE AUTO, granularity AUTO ); END; /cascade TRUE表示连索引统计一起收degree 4是并行度测试环境可以给 4生产上按 CPU 核数的一半左右给别给太高会抢业务资源method_opt里的SIZE AUTO让数据库自己决定哪些列需要直方图比手工FOR COLUMNS SIZE 254更省事granularity AUTO在分区表上会同时收全局和分区级统计。4. PL/SQL 与批量写入把逻辑放进数据库的边界在哪“业务逻辑要不要放存储过程”这个问题在 oracle项目实战 里没有标准答案但有清晰的边界跨多张表、需要事务原子性、数据量大到不适合网络往返的批处理放进去合理纯业务规则、经常改、需要单测的放应用层。真正的问题不是放不放而是放进去之后有没有人写得对。4.1 存储过程与包的骨架异常不要用 WHEN OTHERS THEN NULL用包package而不是散装过程好处是规范与实现分离改包体不影响依赖对象的编译状态也方便按模块授权。CREATE OR REPLACE PACKAGE pkg_order AS PROCEDURE create_order( p_cust_id IN NUMBER, p_amount IN NUMBER, p_order_no OUT VARCHAR2 ); END pkg_order; / CREATE OR REPLACE PACKAGE BODY pkg_order AS -- 自治事务写日志不管主事务回滚与否日志都要留下 PROCEDURE log_err(p_msg IN VARCHAR2) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO t_err_log(log_time, msg) VALUES (SYSTIMESTAMP, SUBSTR(p_msg, 1, 2000)); COMMIT; END log_err; PROCEDURE create_order( p_cust_id IN NUMBER, p_amount IN NUMBER, p_order_no OUT VARCHAR2 ) IS v_id NUMBER(19); BEGIN IF p_amount 0 THEN RAISE_APPLICATION_ERROR(-20100, 金额不能为负); END IF; SELECT seq_order_id.NEXTVAL INTO v_id FROM dual; p_order_no : ORD || TO_CHAR(SYSDATE, YYYYMMDD) || LPAD(v_id, 10, 0); INSERT INTO t_order(order_id, order_no, cust_id, amount, created_at) VALUES (v_id, p_order_no, p_cust_id, p_amount, SYSTIMESTAMP); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN log_err(订单号冲突: || p_order_no); RAISE_APPLICATION_ERROR(-20101, 订单号已存在请重试); WHEN OTHERS THEN log_err(create_order 失败: || SQLERRM || || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE); RAISE; -- 关键记录完继续往上抛别把异常吞掉 END create_order; END pkg_order; /两个地方值得细说。PRAGMA AUTONOMOUS_TRANSACTION让日志写入独立于主事务提交主事务回滚时日志还在——这是排查线上问题最有用的一招否则你永远不知道失败瞬间发生了什么。DBMS_UTILITY.FORMAT_ERROR_BACKTRACE会给出报错的行号比SQLERRM只有一句“数值溢出”有用得多。最忌讳的写法是WHEN OTHERS THEN NULL;。它把异常吃掉了上层应用以为调用成功数据却没写进去等对账发现少单已经是几天后。血泪经验任何WHEN OTHERS后面必须跟log_err和RAISE两条缺一不可。RAISE_APPLICATION_ERROR(-20100, ...)的错误码范围是 -20000 到 -20999用这个区间别用正数正数会和系统错误码撞上。应用侧捕获时按这个区间判断业务异常其他一律当系统异常告警。4.2 批量写入BULK COLLECT 与 FORALL 的边界一行一行INSERT在 PL/SQL 里是灾难。每次执行都要上下文切换PL/SQL 引擎 ↔ SQL 引擎1 万行数据可能跑十几秒。批量绑定能把切换次数降到 1/N。DECLARE CURSOR c_src IS SELECT order_id, order_no, cust_id, amount, created_at FROM t_order_stage WHERE status READY; TYPE t_tab IS TABLE OF c_src%ROWTYPE INDEX BY PLS_INTEGER; l_tab t_tab; l_cnt NUMBER : 0; BEGIN OPEN c_src; LOOP -- LIMIT 2000每次只取一批避免 PGA 撑爆 FETCH c_src BULK COLLECT INTO l_tab LIMIT 2000; EXIT WHEN l_tab.COUNT 0; -- SAVE EXCEPTIONS单条失败不中断整批 BEGIN FORALL i IN 1 .. l_tab.COUNT SAVE EXCEPTIONS INSERT INTO t_order(order_id, order_no, cust_id, amount, created_at) VALUES (l_tab(i).order_id, l_tab(i).order_no, l_tab(i).cust_id, l_tab(i).amount, l_tab(i).created_at); EXCEPTION WHEN OTHERS THEN FOR j IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP DBMS_OUTPUT.PUT_LINE( index || SQL%BULK_EXCEPTIONS(j).ERROR_INDEX || code || SQL%BULK_EXCEPTIONS(j).ERROR_CODE); END LOOP; END; l_cnt : l_cnt l_tab.COUNT; COMMIT; -- 按批提交别攒到最后 l_tab.DELETE; END LOOP; CLOSE c_src; DBMS_OUTPUT.PUT_LINE(total || l_cnt); END; /LIMIT 2000这个数字是经验值。太小比如 100起不到批量效果太大比如 10 万会让 PGA 里堆大量数据并发一高就报ORA-04030: out of process memory。2000 到 5000 之间比较稳具体看单行宽度。SAVE EXCEPTIONS配合SQL%BULK_EXCEPTIONS能把整批里失败的行挑出来继续跑。没有它一行违反约束整批回滚重跑还得从头来。注意ERROR_INDEX是这批数组里的下标不是主键值要拿到主键得回数组里取l_tab(idx).order_id。按批COMMIT是有意为之。攒到循环结束再提交undo 会一直涨中间出问题整个回滚几百万行白跑而且长事务还会加剧ORA-01555的风险。每批提交的代价是如果中途失败已经提交的部分不会回滚——所以这类批处理必须做成幂等用MERGE INTO或者先按批次号删了再插保证重跑结果一致。还有一个替代思路是外部表external table或SQL*Loader直接加载文件比走 PL/SQL 快一个量级。判断标准很简单数据源是文件就用SQL*Loader是库内表到表就用INSERT ... SELECT配合/* APPEND */只有带复杂业务校验、需要逐行判断的场景才值得写 PL/SQL 循环。5. 避坑与排查oracle项目实战 中最容易翻车的五个点5.1 隐式类型转换把索引打瘸现象cust_id是NUMBER索引齐全但应用传参传成字符串后TABLE ACCESS FULL查询从毫秒级掉到秒级。原因WHERE cust_id 10086里Oracle 会把cust_id隐式转成字符串比较等价于TO_CHAR(cust_id) 10086。列上套了函数普通 B-tree 索引直接失效。反过来如果列是VARCHAR2传数字进去也会发生同样的转换。解决统一参数类型JDBC 用setLong而不是setString。已经上线改不动的建函数索引兜底CREATE INDEX idx_cust_char ON t_order(TO_CHAR(cust_id));。排查时看执行计划里的filter谓词如果显示TO_CHAR(CUST_ID)...就是这个问题。5.2 空字符串等于 NULL 带来的连锁反应现象前端提交了空串插入成功但WHERE remark 查不到任何记录或者唯一索引在该列上放了多条空串也不报冲突。原因Oracle 里和NULL是同一个东西这是它和其他数据库最大的语义差异。remark 等价于remark NULL永远不成立唯一索引允许多个 NULL。解决业务上用IS NULL判断别用 需要在空串上做唯一约束的加一个函数索引CREATE UNIQUE INDEX uk_remark ON t(VSIZE(remark))或者写NVL(remark, #)的函数索引。迁移项目里这条最容易被忽略因为源库允许空串迁过来语义就变了。5.3 大事务与 ORA-01555现象一个跑了两小时的批量更新最后报ORA-01555: snapshot too old: rollback segment number ... too small前面全白干。原因undo 表空间被覆盖。查询需要的一致性读镜像已经被后来的事务冲掉回滚段里找不到那个时间点的数据版本。本质是“查询时间太长”加“undo 保留太短”两个条件同时成立。解决拆批处理每批几千行提交一次调大undo_tablespace和undo_retention比如从 900 秒提到 7200 秒确认undo_management是AUTO。根治办法还是缩短单次事务的时间跨度别指望靠调参解决。5.4 统计信息陈旧导致执行计划漂移现象同一套代码昨天跑 200 毫秒今天跑 20 秒SQL 一个字没改。原因夜间窗口没有收集统计信息或者收集时采样比例太低。数据分布变了优化器基于旧统计估错了行数选错了连接方式。还有一种更隐蔽的绑定变量窥探第一次执行时的绑定值恰好选择性极高计划被固定住后面来了低选择性的值也照用。解决先看DBA_TAB_STATISTICS里的LAST_ANALYZED超过一周就要警觉用ALLSTATS LAST对比E-Rows和A-Rows定位偏差确认无误后手工GATHER_TABLE_STATS。防止再次漂移的做法在下一章讲。5.5 字符集与 NLS 导致的导入乱码现象从源库导出的数据导入后中文变成问号或者方块英文数字全对。原因NLS_LANG环境变量和数据库字符集不匹配。客户端按NLS_LANG声明的编码解析字节流声明错了双字节字符就被当成单字节拆开。用exp/imp还是有impdp只要中间经过一次文本转换就会出问题。解决先查SELECT * FROM nls_database_parameters WHERE parameter LIKE %CHARACTERSET%;客户端NLS_LANG设成语言_地区.客户端字符集且客户端字符集与库一致或为其超集。跨字符集迁移直接用Data Pump的TRANSPORT_TABLESPACES做物理搬迁绕开字符转换。6. 备份与计划固化上线之后还能抓住的那点确定性系统上线之后能改的东西变少了但有两件事必须提前做否则出事时只能干看着。第一件是备份。RMAN 的最小可用策略是每周一次全备每天一次增量归档日志单独存放且定期清理。# 全备 归档日志保留 7 天 rman target /CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 7 DAYS; CONFIGURE CONTROLFILE AUTOBACKUP ON; BACKUP DATABASE PLUS ARCHIVELOG FORMAT /backup/rman/full_%U TAG weekly_full; DELETE NOPROMPT OBSOLETE;CONTROLFILE AUTOBACKUP ON必须开否则控制文件丢了备份集里的数据文件根本对不上号。RECOVERY WINDOW OF 7 DAYS是按“能恢复到 7 天内任意时间点”来算保留策略的比按份数保留更贴合实际需求。备份完一定要做一次RESTORE ... VALIDATE没验证过的备份等于没有备份。第二件是执行计划固化。前一章说的计划漂移根治手段是 SQL Plan Baseline把确认过的执行计划记下来优化器以后只能在这份清单里挑不会自己跑到更差的路上去。-- 从游标缓存里为某条 SQL 创建基线 DECLARE v_sql_id VARCHAR2(13); BEGIN SELECT sql_id INTO v_sql_id FROM v$sql WHERE sql_text LIKE SELECT order_id, amount FROM t_order% AND rownum 1; DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id v_sql_id); END; / -- 查看基线状态 SELECT sql_handle, plan_name, enabled, accepted, fixed FROM dba_sql_plan_baselines;LOAD_PLANS_FROM_CURSOR_CACHE把当前缓存里的计划收进基线accepted YES表示优化器可以使用它。fixed YES是强约束除非有更高优先级的提示否则只走这一条——慎用万一当初收进去的计划本身就有问题等于把自己锁死了。稳妥做法是保持fixed NO让优化器在基线集合内挑成本最低的新计划进来时标记为未接受人工确认后再evolve。一个习惯每次大版本发布前把核心业务的十几条 SQL 的sql_id和基线状态导出一份发版后对比。上个月就是靠这个对比发现一条报表 SQL 悄悄换了连接方式从HASH JOIN变成NESTED LOOPS数据量大的时候差了四十倍。数据库不像应用它不会告诉你“我换了条路走”只能自己盯。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?