简介:面向Oracle数据库初学者与需要完成课程设计/项目实战的读者,这份文档以招商银行某分行的开放式基金交易平台为案例,完整呈现数据库设计全流程。内容覆盖需求描述、问题分析、相关技术与工具、阶段划分与项目总结,并给出基金公司表、基金表、活期账户表、理财账户表、基金账户表、基金购买表、交易表等核心数据表的详细字段定义、数据类型、主外键关系及业务状态说明,可直接用作课堂练习或项目参考。资源共1个文件,为doc格式文档,压缩包大小约245KB,下载后即可离线查看。目前已有449人浏览学习。文档从业务需求逐步推导到数据表结构,清晰展示了如何将基金管理、理财账户、交易审核等实际金融场景转化为Oracle数据库模型,可帮助读者掌握数据库设计思路、建表语句编写与项目文档组织方法,是一份实用性较强的Oracle项目实战资料。
1. oracle项目实战:不是会写SQL就能上线,关键在交付链路
很多从 MySQL 转过来的开发,第一次把 Oracle 放进项目里,最先翻车的往往不是 SQL 不会写,而是本地连得上、测试环境乱码、一上生产就锁表。这里说的 oracle 项目实战,不是背几个命令,而是把选型安装、表设计、编码、连接池、部署运维这条交付链路完整跑通。它能帮后端开发减少上线前的低级事故,也能帮运维接手老 Oracle 项目时有具体的排查路径。我会围绕一个带订单库存的常见业务系统展开,每个环节尽量给出能直接改用的命令和参数,遇到该踩的坑也会直接说出来。
2. 从选型到安装:跑通Oracle要过的第一道关
2.1 版本与安装方式:新项目用19c,老项目别乱升
版本怎么选,主要看项目是新建还是接盘。新项目我一般直接上 Oracle 19c,它是目前生命周期里比较舒服的长期支持版本;如果本机学习或维护存量库,11g、12c 也完全够用,前提是别在生产环境追新。这里有个容易被忽略的点:Oracle 的版本支持和 JDK 一样有支持窗口,采购前最好先看自己公司有没有对应授权,别在合规上埋雷。
安装方式常见有三条路:Linux 图形安装、静默安装、Docker 起容器。图形安装在虚拟机里点下一步就行,真正要自动化落地的是静默安装。下面是我常用的最小静默安装命令:
# 解压安装包后,进入 database 目录执行 ./runInstaller -silent \ -responseFile /home/oracle/db_install.rsp \ -ignorePrereqFailurersp 响应文件里的关键参数包括 ORACLE_HOME、ORACLE_BASE、UNIX_GROUP_NAME 和 oracle.install.option。INSTALL_TYPE 选 EE(企业版)还是 SE(标准版),影响后面能不能用分区、RAC 这些特性;业务量没到一定规模,标准版完全够用,买企业版就是多花钱。
装完数据库软件还要装监听和建库。监听用netca -silent配置,建库用dbca -silent,这两个命令同样需要响应文件。如果只是本地开发,Docker 是最省事的选择,常见做法是拉官方镜像后通过环境变量指定 SYS 密码和字符集,几分钟就能得到一个可用的测试库。需要注意容器里的 Oracle 对内存要求不低,Docker Desktop 默认 2G 内存很容易在启动阶段就报 ORA-27102,先把 Docker 内存调到 4G 以上再跑。
2.2 内存、字符集、进程数:一启动就出幺蛾子的三个参数
Oracle 安装完不是直接就能扛住业务压力的。搞数据库项目的人都知道,配置里最影响“能不能稳定运行”的就是内存、字符集和进程数。
内存方面,SGA 和 PGA 是两个大头。SGA 包括数据缓冲、共享池,PGA 是排序和哈希用的内存。一般经验是 SGA 加 PGA 控制在物理内存的 70% 左右,留一些给操作系统。很多翻车现场是把 memory_target 直接设成物理内存 80% 以上,一启动服务器就卡死,因为还有别的进程在跑。下面两条命令是项目里最常用的内存查看与修改方式:
-- 查看当前值 SHOW PARAMETER sga_target; SHOW PARAMETER pga_aggregate_target; -- 修改(需要重启实例生效) ALTER SYSTEM SET sga_target=2G SCOPE=spfile; ALTER SYSTEM SET pga_aggregate_target=1G SCOPE=spfile;参数里的 SCOPE 有三个值:spfile 表示写进服务器参数文件、下次启动生效;memory 表示只改当前实例、重启后丢失;both 表示两者都改。SGA 这类内存参数一般只支持 spfile 方式,如果直接用 both 可能会报参数不受支持。改完之后要重启实例,用STARTUP验证。
字符集是另一个能把人逼疯的点。数据库字符集在建库时确定,建议直接用 AL32UTF8,对应的是 UTF-8。如果建库时选了 ZHS16GBK,后面再迁 UTF-8 就要做字符集转换,非常痛苦。客户端连接乱码的大多数原因不是数据库字符集,而是 NLS_LANG 环境变量写错了。NLS_LANG 的标准格式是“语言_地区.字符集”,例如:
export NLS_LANG=AMERICAN_AMERICA.AL32UTF8把这个写进应用启动脚本,就能避免“数据库里看正常,Java 里读出来全是问号”这类常见问题。顺便说一句,我在排查乱码时一般会先跑SELECT USERENV('LANGUAGE') FROM dual;,确认当前会话的语言环境到底是什么,比反复重启应用高效得多。
进程数这个参数最容易和连接池扯上关系。默认 PROCESSES=150 对开发库够用,但只要应用一接连接池,很容易触发 ORA-12520 或 ORA-00020。我一般会把 PROCESSES 调到 500 或更高,同时注意和系统 ulimit 匹配,否则进程数调上去了但 OS 会话限制不够,照样连不进来。
2.3 安装完成后的一组最小验证命令
装完环境后不要急着建表,先用一组命令确认基础服务正常。下面这张表是我每次在新环境都会过的验证项,照着跑一遍能省掉后面大量“为什么连不上”的排查时间。
| 命令 | 期望结果 | 作用 |
|---|---|---|
tnsping ORCL | OK 或类似成功信息 | 检查网络和监听连通性 |
lsnrctl status | 显示实例服务已注册 | 确认监听服务状态 |
sqlplus / as sysdba | 登录成功 | 检查本机管理员登录 |
SELECT status FROM v$instance; | OPEN | 确认实例启动状态 |
tnsping 通过是客户端到监听通;sqlplus 能登录是本地认证和实例没问题;v$instance 显示 OPEN 才说明数据库真正对外服务。有一个很常见的认知误区:数据库实例已经启动,但监听没起来,客户端照样是连不上的,因为连接请求是被监听转发给实例的。所以上面四个命令建议按顺序跑,缺哪个补哪个。
3. 项目开发绕不开的四个点:分页、序列、存储过程与dual
3.1 分页查询:ROWNUM的陷阱和OFFSET FETCH的性能边界
在 Oracle 里做分页,老项目里最常见的是三层嵌套 ROWNUM。很多人第一次写会直接WHERE ROWNUM > 5,结果一条数据都查不出来,原因在于 ROWNUM 是结果集生成过程中逐行分配的编号,分配发生在 WHERE 过滤之前,ROWNUM > 5意味着“第一行不满足就扔掉,第二行又重新从1开始数”,永远数不到5。理解这个机制之后,再写三层嵌套就顺理成章了:
-- 12c 之前最经典的分页写法 SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT order_id, customer_id, amount, create_time FROM orders ORDER BY create_time DESC ) t WHERE ROWNUM <= :pageSize * :pageNo ) WHERE rn > (:pageNo - 1) * :pageSize;这里用绑定变量 :pageSize 和 :pageNo,避免拼接字符串带来的 SQL 注入风险,也方便 Oracle 做语句共享。内层 ORDER BY 必须存在,否则每次查询的行序可能不同,翻页就会出现记录重复或漏掉。如果排序字段有重复值,比如 create_time 相同,最好在 ORDER BY 里加一个不重复字段如 order_id 做第二排序键,才能保证分页稳定。
12c 之后有了标准化的 OFFSET FETCH 语法,写起来更像 MySQL 的 LIMIT:
SELECT order_id, customer_id, amount FROM orders ORDER BY create_time DESC OFFSET :offset ROWS FETCH NEXT :pageSize ROWS ONLY;注意它虽然简洁,但 OFFSET 越大,数据库要跳过前面越多的行,翻到第 100 页时性能就会明显下降。经典的 ROWNUM 写法在深分页场景同样存在这个毛病,本质上都需要“先排序再取一段”。真正要解决深分页,一般是加一个游标或者用 ID 范围分段,也就是 WHERE order_id > 上次最大ID 然后 FETCH NEXT;这种手法在后台数据导出场景特别实用。
3.2 序列:并发下主键生成别用MAX(id)+1
业务表的主键如果让应用层去查 MAX(id)+1,两个并发事务同时查出来同一个值,插入时就会出现主键冲突。Oracle 里标准做法是序列。项目实战里创建序列时,几个参数要按业务来定:
CREATE SEQUENCE seq_order_id START WITH 10000 INCREMENT BY 1 CACHE 100 NOCYCLE;START WITH 从多少开始,常见场景是为了和旧数据错开。CACHE 100 表示预生成 100 个序列值放到内存,能显著减少每次 NEXTVAL 都写磁盘的开销;代价是数据库一旦崩溃,缓存里未使用的序列值会丢掉,所以序列会出现空洞。如果业务对“编号连续”有强迫症,就设 NOCACHE,否则别把 CACHE 改成 0。NOCYCLE 是默认推荐,序列到达最大值后不会回绕,避免主键冲突。
使用序列时,CURRVAL 有个限制:必须先在当前会话执行过一次 NEXTVAL,否则会报 ORA-08002。很多新手在插入主表后又想拿主键插入子表,会先SELECT seq_order_id.CURRVAL FROM dual,如果中间连接池换了连接,就会拿到一个错误的值。正确做法是在同一个事务里、同一个连接上记录 NEXTVAL,或者直接把 NEXTVAL 写进 INSERT 的 VALUES 里,再用 RETURNING 取出来。
3.3 存储过程:绑定变量、OUT参数和异常处理的现场经验
如果你的业务有比较复杂的写库逻辑,比如“创建订单 → 扣库存 → 写流水”需要在一个事务里完成,我一般会把它包进存储过程。这样应用只需要传几个参数,数据库内部完成事务控制,网络往返也少。一个最基础的示例:
CREATE OR REPLACE PROCEDURE sp_create_order( p_customer_id IN NUMBER, p_product_id IN NUMBER, p_amount IN NUMBER, p_order_id OUT NUMBER ) IS BEGIN SELECT seq_order_id.NEXTVAL INTO p_order_id FROM dual; INSERT INTO orders(order_id, customer_id, product_id, amount, status) VALUES (p_order_id, p_customer_id, p_product_id, p_amount, 'NEW'); UPDATE product_stock SET stock = stock - 1 WHERE product_id = p_product_id; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END sp_create_order; /这里 OUT 参数把新建的订单号返回给应用层,省得应用再查一次。COMMIT 放在存储过程内部,能保证“插入订单 + 扣库存”作为一个完整事务提交;如果中途任何一步失败,EXCEPTION 块里的 ROLLBACK 会把整个事务回滚,WHEN OTHERS 里的 RAISE 保留原始错误,应用层才能拿到真实报错而不是被吞掉的自定义错误。
需要说明的是,存储过程不是越多越好。新项目里如果团队对 PL/SQL 不熟,把大量业务逻辑塞进去,不仅 Git 管理困难,测试也麻烦。我比较推荐的做法是:事务边界清晰的批量导入、对账、报表这类逻辑用存储过程;普通 CRUD 还是交给 Java/Python 应用层写 SQL。Oracle EBS 这类套件里的 WIP 非标工单、成本计算,底层全是长事务和存储过程,这时候才值得把复杂逻辑彻底沉到数据库里。
3.4 dual表与trunc(sysdate):两个高频小操作的正确姿势
dual 是 Oracle 特有的“伪表”,表结构里只有一个字段,但它的作用不是存数据,而是提供一个可以从 SELECT 语句里取常量的对象。SELECT SYSDATE FROM dual能返回当前时间,SELECT seq_order_id.NEXTVAL FROM dual能取序列值。网上有人问 dual 最多存多大,这是个无效问题,它本身就不是业务表,不会也不应该承载数据。
trunc(sysdate) 在报表和统计 SQL 里高频出现,作用是把时间截断到指定精度。比如:
SELECT SYSDATE FROM dual; -- 当前时间 SELECT TRUNC(SYSDATE) FROM dual; -- 当天零点 SELECT TRUNC(SYSDATE, 'MM') FROM dual; -- 本月第一天零点 SELECT TRUNC(SYSDATE, 'YYYY') FROM dual; -- 今年第一天零点在用日期做条件查询时,很多开发者会写WHERE TRUNC(create_time) = TRUNC(SYSDATE),这个写法在业务数据量小的时候没毛病,一旦 create_time 上有索引,TRUNC(create_time) 会导致索引失效,变成全表扫描。我一般会改成WHERE create_time >= TRUNC(SYSDATE) AND create_time < TRUNC(SYSDATE) + 1,这样既拿到当天整段数据,又能让日期列上的索引正常命中。这是项目实战里报表 SQL 优化最常见的一处改动,效果立竿见影。
4. 前后端分离项目里的Oracle连接:JDBC与Python两种主流写法
4.1 JDBC连接串与PreparedStatement:别再用拼接SQL了
前后端分离架构里,数据库操作基本都收在服务端,API 层只暴露 JSON。Java 后端连 Oracle 最常见的驱动是 thin 驱动,连接串格式要分清两种:
String url = "jdbc:oracle:thin:@//192.168.1.100:1521/orcl";这里的//host:port/service_name是 12c 之后推荐的服务名方式;还有一种旧格式jdbc:oracle:thin:@host:port:SID,很多存量项目还在用。如果连接串写错,最常见的报错是 ORA-12505,它表示监听收到了请求,但找不到连接串里的服务名对应的服务;不是密码错,也不是网络不通,很多人在这里反复改密码浪费时间。
连接之后第一件事,就是用 PreparedStatement 而不是 Statement。除了防 SQL 注入,PreparedStatement 还能让 Oracle 复用执行计划:
String sql = "SELECT order_id, amount FROM orders WHERE create_time >= ? AND status = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now().minusDays(1))); ps.setString(2, "PAID"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { long orderId = rs.getLong("order_id"); BigDecimal amount = rs.getBigDecimal("amount"); // 组装 DTO 返回给前端 } } }注意金额字段用 BigDecimal 而不是 double,Oracle 的 NUMBER 精度比 float 可靠,用 double 会导致金额出现细微误差,这类问题在报表对账时非常难查。
4.2 连接池配置:HikariCP按这些参数调能少一半告警
Java 项目部署实战里,连接池是必考项。不用连接池的 HTTP 接口,每来一个请求就新建连接,数据库在并发一高时直接 ORA-12520,也就是进程数不够。HikariCP 是现在 Spring Boot 默认连接池,核心参数有五个:
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:oracle:thin:@//192.168.1.100:1521/orcl"); config.setUsername("app_user"); config.setPassword("app_pass"); config.setMaximumPoolSize(20); config.setMinimumIdle(2); config.setConnectionTimeout(3000); config.setMaxLifetime(1800000); config.setConnectionTestQuery("SELECT 1 FROM dual");maximumPoolSize 不是越大越好。Oracle 的连接背后要占内存和进程,20 个连接对多数业务足够;如果某个接口把连接拿住不放,连接池满了,后续请求就会排队等 connectionTimeout,超过 3 秒抛异常。maxLifetime 设 30 分钟,是让连接周期性重建,避免池里的连接被数据库或防火墙静默断开。
connectionTestQuery 在 HikariCP 里并不是必须的,JDBC4 驱动自带的 isValid 已经能做连接存活校验。但 Oracle 在部分网络环境下会出现“连接看起来活着、实际已死”的情况,也就是第一次查询报 IO 错误。我一般会加上这个配置,同时在 JDBC 连接串里追加oracle.net.CONNECT_TIMEOUT=5000和oracle.jdbc.ReadTimeout=60000,让连接建立和读数据都有明确的超时边界,而不是无限等下去。
4.3 Python连接Oracle查询数据:新老库的导入区别
Python 连 Oracle 常见操作是安装 oracledb 或 cx_Oracle。新库建议直接使用 oracledb,老项目里大量用的 cx_Oracle 也还能跑,核心用法几乎一致,主要区别是导入包名和初始化方式。下面是最小查询脚本:
import oracledb from datetime import datetime conn = oracledb.connect( user="scott", password="tiger", dsn="192.168.1.100:1521/orcl" ) with conn.cursor() as cur: cur.execute( "SELECT order_id, amount, create_time FROM orders WHERE create_time >= :1", (datetime.now().replace(hour=0, minute=0, second=0, microsecond=0),) ) for row in cur: print(row[0], row[1], row[2]) conn.close()这里的:1是位置绑定参数,和 Java 的?类似,能避免把日期直接拼进 SQL。Python 端要注意时区:Oracle 的 DATE 类型不带时区,如果应用服务器和数据库服务器时区不一致,取出来的 create_time 会和你预期差几个小时。我一般约定所有时间字段用数据库服务器本地时区,应用层只做展示不做转换。
Mac 上使用 Python 或 Navicat 连 Oracle 时,如果报错提示未加载某种库,基本不是 SQL 问题,而是本机缺少 Oracle Instant Client。常见做法是下载和数据库版本匹配的 Instant Client,并把它的路径配置到动态库查找环境中。要注意新版 Mac 对动态库环境变量有权限限制,设置不生效时,优先参考当前使用的 Oracle 客户端工具给出的官方配置说明,不要硬改系统文件。
4.4 事务和提交边界:批量更新也能把undo塞满
批量更新是生产环境出问题最多的地方。比如把订单状态批量改成“已对账”,代码里最常见的错误是每循环一条就 commit 一次,几百条数据能提交几百次,性能差不说,中途失败还会留下半截数据。正确做法是分段提交或一次性提交:
try (Connection conn = dataSource.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement( "UPDATE orders SET status = ? WHERE order_id = ?")) { for (int i = 0; i < orderList.size(); i++) { Order o = orderList.get(i); ps.setString(1, o.getStatus()); ps.setInt(2, o.getOrderId()); ps.addBatch(); if (i % 500 == 499) { ps.executeBatch(); conn.commit(); } } ps.executeBatch(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } }这里的 500 是一个经验值,批太大执行时间太长,undo 和锁持有时长都会增加;批太小又频繁提交,效果不明显。commit 没做好的另一个后果是 undo 表空间暴涨,Oracle 的 undo 保存的是修改前的数据,长事务一直不提交,undo 里的旧版本就不能清掉,查询会越跑越慢。如果你发现某个批处理脚本运行后 v$undostat 里的 undo 增长异常,基本是 commit 边界没控制住。
5. 部署与运维避坑:监听、日志、自启和等保这四处高频现场
5.1 现象:监听服务无法启动,远程客户端全部连不上
现象:本地 sqlplus 能登录,但应用服务器或另一台机器用 JDBC 连接时报 ORA-12541、ORA-12514;或者执行 lsnrctl start 直接提示无法监听。
原因:最常见的是三件事。第一,/etc/hosts 里主机名解析不一致,Oracle 安装时生成 listener.ora 用的是安装时的 hostname,如果后来改过主机名,监听就找不着地址。第二,1521 端口被防火墙拦截,本地监听状态看着正常,远程访问却超时。第三,listener.ora 里的 SERVICE_NAME 与应用连接串里的服务名对不上,导致 ORA-12514。
解决:先执行下面两条命令,把状态和最近日志捞出来:
lsnrctl status lsnrctl show config tail -n 80 $ORACLE_BASE/diag/tnslsnr/$(hostname -s)/listener/trace/listener.log确认监听实际监听地址是 0.0.0.0 而不是 127.0.0.1;再看防火墙有没有放行 1521。服务名对不上的,把应用连接串改成和lsnrctl services里看到的一致。最后用sqlplus scott/tiger@host:1521/service_name从远程测一次,比在应用里翻日志快得多。
5.2 现象:监听日志把磁盘撑爆了
现象:告警磁盘使用率高,排查发现 $ORACLE_BASE/diag 目录占了几个 GB 甚至几十 GB;打开 listener.log 一看全是重复连接记录。
原因:listener.log 是追加写入的,不会自动轮转。项目里只要有大量重连、配置错误的客户端反复尝试,日志就能指数级增长。我见过最夸张的一次是连着 7 天没管,40GB 磁盘被监听日志填满,数据库直接挂掉。
解决:不能直接删 listener.log,因为文件被监听进程占用,直接删不会释放磁盘空间。标准做法是先停监听,把原日志改名保留,再启动监听:
lsnrctl stop LISTENER mv $ORACLE_BASE/diag/tnslsnr/$(hostname -s)/listener/trace/listener.log \ $ORACLE_BASE/diag/tnslsnr/$(hostname -s)/listener/trace/listener.log.$(date +%Y%m%d) lsnrctl start LISTENER改名后旧文件虽然还占着句柄,但新日志会写进新建的 listener.log,停掉监听后旧文件句柄才被真正释放。要根治,可以配置 log_status=OFF 关闭监听日志,或者在运维脚本里按月归档清理。对老版本 Oracle 的习惯,直接删掉也能用,但现在的 11g 以后版本更推荐用自动诊断库的清理命令来收。
5.3 现象:Linux 重启后 Oracle 和监听都不自动起来
现象:机房断电或服务器重启后,应用全挂,人工执行 sqlplus 又能正常连接。
原因:Oracle 安装完成后默认没有把数据库实例和监听注册成 Linux 自启动服务。Linux 重启后监听进程和数据库进程都不会自动拉起。
解决:生产环境我一般用 systemd 管理。先确认 /etc/oratab 里对应实例行的最后一个字段是 Y,这个字段控制 dbstart 是否启动该实例:
# 如果文件里是 N,改成 Y # orcl:/opt/oracle/product/19c/dbhome_1:Y然后写一个 systemd 服务,指向 dbstart 和 dbshut:
[Unit] Description=Oracle Database After=network.target [Service] User=oracle Group=oinstall Type=forking ExecStart=/opt/oracle/product/19c/dbhome_1/bin/dbstart /opt/oracle/product/19c/dbhome_1 ExecStop=/opt/oracle/product/19c/dbhome_1/bin/dbshut /opt/oracle/product/19c/dbhome_1 RemainAfterExit=yes [Install] WantedBy=multi-user.target写好后执行systemctl enable oracle-db,重启服务器验证一遍。注意 dbstart 需要 ORACLE_HOME 和 ORACLE_UNQNAME 环境变量,在 systemd 文件里加 Environment 行更稳妥。这一步做完能省掉大半夜被叫起来手动 restart 的痛苦。
5.4 现象:等保检查要出的安全参数不知道去哪查
现象:等保测评或内审要求提供数据库安全配置,包括口令策略、登录失败锁定、审计开关,临时去翻文档找不到对应 SQL。
原因:Oracle 的安全配置分散在 profile、参数文件和初始化参数里,不熟悉的人确实找不到。
解决:下面三组 SQL 能覆盖大部分检查项:
-- 口令策略与资源限制 SELECT profile, resource_name, limit FROM dba_profiles WHERE profile = 'DEFAULT' AND resource_name IN ('FAILED_LOGIN_ATTEMPTS','PASSWORD_LIFE_TIME','PASSWORD_LOCK_TIME'); -- 审计开关 SELECT name, value FROM v$parameter WHERE name LIKE 'audit%'; -- 数据库版本和补丁信息 SELECT banner FROM v$version;等保测评里常见的整改点包括:FAILED_LOGIN_ATTEMPTS 设成 5 或 10 次,PASSWORD_LIFE_TIME 设 90 天,PASSWORD_LOCK_TIME 设 1 天;审计开关至少打开基本审计,具体要求按测评表来,别自己凭感觉改。改 profile 用 ALTER PROFILE,改完对新建会话生效;已经存在的用户要跑ALTER USER ... PROFILE DEFAULT或等待下次登录重新加载。
6. 上线前用这三类手段验证Oracle项目能不能扛住
项目上线前一天,我都会做三件固定检查,比跑通用例更能发现问题。
第一,用执行计划验证慢查询。开发时数据量小,全表扫描也感觉不到慢;上了生产数据量一大,全表扫描就是事故。导出一小段生产数据到测试环境,对核心 SQL 跑一遍:
EXPLAIN PLAN FOR SELECT * FROM orders WHERE create_time >= TRUNC(SYSDATE); SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);看计划里有没有全表扫(TABLE ACCESS FULL),有没有大表排序(SORT ORDER BY)。如果有,优先检查索引和 SQL 写法。
第二,用 v$ 视图看会话和锁。上线验证时并发一高,锁等待就是最典型的故障前兆:
SELECT sid, serial#, event, blocking_session FROM v$session WHERE wait_class != 'Idle';如果出现大量 enq: TX - row lock contention,说明有事务互相等锁,要先看是不是没有 commit。
第三,用闪回查询给误操作留后悔药。上线首周数据被误删是大几率事件,不要只指望备份。闪回查询可以查过去某时刻的数据:
SELECT order_id, amount FROM orders AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '10' MINUTE) WHERE order_id = 10086;如果查询结果和现在不同,说明中间有过修改或删除,可以据此找回数据。注意闪回依赖 undo 保留时间,undo_retention 一般至少设 900 秒,太短闪回窗口不够用。
我现在养成的习惯,是把上面三条 SQL 写成一个 readycheck.sql 放进项目仓库,每次上线前在测试环境执行一遍,输出结果直接留档。这样线上出问题时能快速对照“上线前的基线长什么样”,排查效率会明显提高。希望帮到你。
本文还有配套的精品资源,点击获取