简介本资源是一份面向Oracle数据库开发与运维人员的实战型技术文档聚焦Long、Raw、Blob三类关键大对象字段的读写操作实践解决实际项目中二进制数据与超长文本存储难、操作易出错等典型问题。文档以Oracle 9i环境为基准完整呈现C_EMP1_T表建表语句、C#代码级INSERT/UPDATE/BLOB文件上传流程含aspx前端控件配置与服务器端File.ReadAllBytes处理、参数绑定细节及类型适配要点覆盖从IC卡MAC号RAW、用户简历LONG到图像文件BLOB的全场景示例。资源为单文件PDF共1个514KB技术文档内容精炼、代码可直接参考。目前已有247人学习下载适合中初级DBA及.NETOracle混合开发工程师快速掌握大对象字段的安全读写规范与避坑要点。1. Oracle里Long、RAW、BLOB这三类“难搞字段”到底在卡谁——不是语法不会是读写逻辑根本不同你在Oracle里建表时随手写了LONG结果Java程序一查就报ORA-00932用PL/SQL往RAW字段插十六进制数据存进去再SELECT出来却变成乱码更别提BLOB——明明文件上传成功前端下载却是空的或损坏的。这不是你SQL写错了而是这三类字段压根不走普通VARCHAR2那套读写路径LONG被Oracle官方标记为“过时但未移除”RAW绕过字符集转换直接存二进制字节流BLOB则必须通过LOB Locator机制操作不能像普通字段一样INSERT INTO t VALUES (xxx)直塞。它们专治“以为数据库字段都一样”的新手也常让有MySQL/PostgreSQL经验的开发者在Oracle项目里集体翻车。本文不讲概念定义只聚焦一线真实场景用最简SQLPL/SQLJDBC三路实操把LONG的兼容读法、RAW的十六进制安全写法、BLOB的分块流式读写全部跑通每一步附参数含义和失败日志特征。适合正在维护老系统、对接遗留接口、或刚接手含LOB字段Oracle库的后端/DBA。2. Long字段为什么它还在又为什么必须用特殊方式读LONG类型是Oracle 7时代遗留的“大文本”方案虽自Oracle 8i起就被官方建议用CLOB替代但大量老系统尤其金融、政务类仍存在。它的核心限制在于单表只能有一个LONG字段且不能出现在WHERE/ORDER BY/GROUP BY子句中更不能参与索引、约束、分区。但真正让开发者崩溃的是读取行为——当你执行SELECT long_col FROM t WHERE id1Oracle客户端如SQL*Plus、SQL Developer默认会截断显示通常只显示前80字符而JDBC驱动若未显式设置setLongDataBuffer会直接抛出SQLException: ORA-01403: no data found即使数据存在。这不是数据丢了是驱动层主动放弃读取。2.1 用SQL*Plus安全读取Long字段的最小配置# 启动SQL*Plus时必须加 -S 参数禁用提示并设置行宽与长字段缓冲区 sqlplus -S username/password//host:port/service_name EOF SET LINESIZE 32767 SET LONG 1000000 SET PAGESIZE 0 SELECT long_col FROM your_table WHERE id 1; EXIT; EOF逻辑说明SET LONG 1000000告诉SQL*Plus最多读取100万字符否则默认只读80SET LINESIZE 32767防止长文本自动换行-S避免输出连接信息干扰解析。若不设LONG值SELECT返回的只是LONG占位符。2.2 PL/SQL中读取Long字段必须用DBMS_SQL包绕过限制DECLARE l_cursor INTEGER; l_long_data VARCHAR2(32767); l_buffer VARCHAR2(32767); l_amount BINARY_INTEGER : 32767; l_offset INTEGER : 1; BEGIN l_cursor : DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE(l_cursor, SELECT long_col FROM your_table WHERE id :id, DBMS_SQL.NATIVE); DBMS_SQL.BIND_VARIABLE(l_cursor, :id, 1); IF DBMS_SQL.EXECUTE_AND_FETCH(l_cursor) 0 THEN -- 关键用DBMS_SQL.COLUMN_VALUE_LONG读取LONG不能用COLUMN_VALUE DBMS_SQL.COLUMN_VALUE_LONG(l_cursor, 1, l_buffer, l_amount, l_offset, l_long_data); DBMS_OUTPUT.PUT_LINE(Long content length: || LENGTH(l_long_data)); DBMS_OUTPUT.PUT_LINE(SUBSTR(l_long_data, 1, 200)); -- 打印前200字符 END IF; DBMS_SQL.CLOSE_CURSOR(l_cursor); END; /参数说明COLUMN_VALUE_LONG是唯一能安全读取LONG的APIl_amount设为32767是最大单次读取长度Oracle限制l_offset从1开始若内容超长需循环调用并累加offset。注意此方法仅适用于PL/SQL环境JDBC需另走路径。2.3 JDBC读取Long字段必须关闭自动提交并显式获取流// Java代码片段使用Oracle JDBC 19c driver String sql SELECT long_col FROM your_table WHERE id ?; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 必须关闭自动提交否则LONG读取失败 ps.setLong(1, 1L); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { // 关键用getAsciiStream()而非getString() InputStream is rs.getAsciiStream(long_col); if (is ! null) { String longContent new String(is.readAllBytes(), StandardCharsets.UTF_8); System.out.println(Long content length: longContent.length()); } } } }踩坑点getString()对LONG字段会返回null或截断getAsciiStream()才是正确入口setAutoCommit(false)是Oracle JDBC驱动硬性要求否则getAsciiStream()返回null。驱动版本低于12.1可能需用getBinaryStream()但语义上LONG应视为ASCII文本。3. RAW字段十六进制存储的“零损耗”通道但写错一个字节就全废RAW是Oracle中唯一原生支持二进制字节存储的标量类型非LOB常用于存加密密钥、哈希值、硬件设备ID等。它不经过字符集转换存什么字节就返什么字节——这是优势也是雷区。比如你用HEXTORAW(FF)插入SELECT出来确实是FF但若误写成HEXTORAW(ff)小写某些旧版驱动会报错更常见的是Java端用getBytes()直接塞字符串结果因平台默认编码如GBK导致字节错乱。RAW字段最大长度2000字节超过必须用BLOB。3.1 SQL中安全写入RAW字段HEXTORAW函数是唯一可信入口-- 正确全大写十六进制字符串长度为偶数 INSERT INTO your_table (id, raw_col) VALUES (1, HEXTORAW(A1B2C3D4E5F6)); -- 错误示例运行时可能报ORA-01465 -- INSERT INTO your_table (id, raw_col) VALUES (1, HEXTORAW(a1b2)); -- 小写在部分版本不支持 -- INSERT INTO your_table (id, raw_col) VALUES (1, HEXTORAW(A1B2C)); -- 奇数长度ORA-01465: invalid hex number -- 查询时用RAWTOHEX确保可读性 SELECT id, RAWTOHEX(raw_col) AS raw_hex FROM your_table WHERE id 1;逻辑说明HEXTORAW()严格校验输入必须是偶数长度、仅含0-9A-F字符RAWTOHEX()是反向转换用于调试。永远不要用UTL_RAW.CAST_TO_RAW(string)存业务数据——它按数据库字符集编码不是纯字节。3.2 PL/SQL中构造RAW用UTL_RAW包拼接避免隐式转换DECLARE l_raw_val RAW(2000); BEGIN -- 安全拼接用UTL_RAW.CONCAT拼接已知HEXTORAW结果 l_raw_val : UTL_RAW.CONCAT( HEXTORAW(A1B2), HEXTORAW(C3D4), HEXTORAW(E5F6) ); INSERT INTO your_table (id, raw_col) VALUES (2, l_raw_val); COMMIT; -- 验证SELECT RAWTOHEX(raw_col) 应返回 A1B2C3D4E5F6 END; /参数说明UTL_RAW.CONCAT接受多个RAW参数无字符集风险UTL_RAW.CAST_TO_RAW(ABC)会将字符串按数据库字符集如AL32UTF8转字节若字符串含中文则结果不可控禁止用于业务数据。3.3 JDBC写入RAW字段必须用setBytes()且字节数组需预处理// Java代码生成确定字节序列不依赖字符串编码 byte[] keyBytes new byte[]{(byte)0xA1, (byte)0xB2, (byte)0xC3, (byte)0xD4}; // 或从十六进制字符串解析推荐避免手写0x String hexStr A1B2C3D4; byte[] parsedBytes parseHexStrToByte(hexStr); // 自定义工具方法 try (PreparedStatement ps conn.prepareStatement(INSERT INTO your_table (id, raw_col) VALUES (?, ?))) { ps.setLong(1, 2L); ps.setBytes(2, parsedBytes); // 关键必须用setBytes()不能setString() ps.executeUpdate(); } // parseHexStrToByte实现安全解析大写十六进制 public static byte[] parseHexStrToByte(String hexStr) { if (hexStr.length() % 2 ! 0) throw new IllegalArgumentException(Hex string length must be even); byte[] result new byte[hexStr.length() / 2]; for (int i 0; i hexStr.length(); i 2) { result[i / 2] (byte) ((Character.digit(hexStr.charAt(i), 16) 4) Character.digit(hexStr.charAt(i1), 16)); } return result; }踩坑点setString()会触发字符集转换setBytes()直传字节parseHexStrToByte必须处理大写A-FOracleHEXTORAW只认大写若用Apache Commons Codec的Hex.decodeHex()需确保传入char数组为大写。4. BLOB字段不是“大字符串”是必须用流操作的独立对象BLOBBinary Large Object是Oracle处理超大二进制数据的标准方案上限4GB。它和LONG本质不同BLOB是独立的LOB段segment表中只存一个指向它的locator定位器所有读写必须通过DBMS_LOB包或JDBC的Blob接口进行。直接INSERT INTO t VALUES (EMPTY_BLOB())只是创建空locator必须用DBMS_LOB.WRITE()或setBinaryStream()填充内容。这也是为什么前端上传文件后查BLOB字段长度为0——locator建了但没人往里面写数据。4.1 PL/SQL中写入BLOB三步法初始化写入提交-- 第一步插入空BLOB locator INSERT INTO your_table (id, blob_col) VALUES (1, EMPTY_BLOB()) RETURNING blob_col INTO :blob_loc; -- 第二步在PL/SQL块中用DBMS_LOB操作locator DECLARE l_blob BLOB; l_bfile BFILE; BEGIN SELECT blob_col INTO l_blob FROM your_table WHERE id 1 FOR UPDATE; -- 方式1从操作系统文件加载需DIRECTORY对象 -- l_bfile : BFILENAME(MY_DIR, image.jpg); -- DBMS_LOB.FILEOPEN(l_bfile, DBMS_LOB.FILE_READONLY); -- DBMS_LOB.LOADFROMFILE(l_blob, l_bfile, DBMS_LOB.GETLENGTH(l_bfile)); -- DBMS_LOB.FILECLOSE(l_bfile); -- 方式2从字节数组写入更常用 DBMS_LOB.WRITEAPPEND(l_blob, 4, UTL_RAW.CAST_TO_RAW(ABCD)); DBMS_LOB.WRITEAPPEND(l_blob, 4, UTL_RAW.CAST_TO_RAW(EFGH)); COMMIT; END; /逻辑说明EMPTY_BLOB()生成空locatorFOR UPDATE锁定行防止并发写冲突DBMS_LOB.WRITEAPPEND追加写入比WRITE更安全无需管理offsetUTL_RAW.CAST_TO_RAW()在此处安全因输入是ASCII字符串。切记没有COMMIT写入不生效。4.2 JDBC写入BLOB用setBinaryStream()分块避免内存溢出// Java代码流式写入不加载整个文件到内存 FileInputStream fis new FileInputStream(/path/to/large_file.zip); try (PreparedStatement ps conn.prepareStatement(INSERT INTO your_table (id, blob_col) VALUES (?, ?))) { ps.setLong(1, 3L); // 关键getBinaryStream()返回OutputStreamwrite()分块写入 try (OutputStream os ((oracle.sql.BLOB) ps.getParameterMetaData().getParameterType(2) oracle.jdbc.OracleTypes.BLOB ? ((oracle.sql.BLOB) ps.getObject(2)).getBinaryOutputStream() : null).orElseThrow()) { // 更可靠写法用setBinaryStream()直接获取OutputStream OutputStream os2 ps.setBinaryStream(2); // Oracle JDBC 12c 支持 byte[] buffer new byte[8192]; int len; while ((len fis.read(buffer)) ! -1) { os2.write(buffer, 0, len); } os2.close(); } ps.executeUpdate(); }参数说明setBinaryStream(2)返回OutputStream直接写入BLOBbuffer大小建议8KB8192过大易OOM过小IO频繁ps.executeUpdate()前必须关闭流否则数据不提交。不要用setBytes(byte[])——它会把整个文件加载进JVM内存100MB文件直接OOM。4.3 JDBC读取BLOB用getBinaryStream()流式下载禁用getBytes()// Java代码安全读取BLOB到文件 String sql SELECT blob_col FROM your_table WHERE id ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setLong(1, 3L); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { InputStream is rs.getBinaryStream(blob_col); // 关键不是getBlob().getBytes() if (is ! null) { Files.copy(is, Paths.get(/tmp/downloaded_file.zip), StandardCopyOption.REPLACE_EXISTING); System.out.println(BLOB saved successfully); } } } }踩坑点getBlob().getBytes()会把整个BLOB加载进内存1GB文件直接崩溃getBinaryStream()返回InputStream可流式处理Files.copy()是JDK7推荐方式自动处理缓冲。5. 避坑指南Long/Raw/Blob三大字段的5个血泪现场这些坑不是文档里写的“注意事项”而是某开发者在凌晨三点重启应用时发现的真问题。每一条都带现象、原因、解决照着查日志就能定位。5.1 现象JDBC查询含LONG字段的表ResultSet.next()返回false但表里明明有数据原因JDBC连接未关闭自动提交conn.setAutoCommit(false)或驱动版本低于12.1未正确处理LONG locator。解决强制设置conn.setAutoCommit(false)升级Oracle JDBC驱动至19c以上改用getAsciiStream()读取。5.2 现象PL/SQL中SELECT raw_col INTO l_raw FROM t后DBMS_OUTPUT.PUT_LINE(RAWTOHEX(l_raw))输出NULL原因raw_col字段值为NULL但RAWTOHEX(NULL)返回NULL而非空字符串容易误判为查询失败。解决先检查l_raw IS NULL再调用RAWTOHEX或用NVL(RAWTOHEX(raw_col), NULL)在SQL层处理。5.3 现象Java用setBytes()写入RAW字段SELECT出来十六进制值与预期不符如A1B2变成C2A1C2B2原因Java字节数组构造错误如用A1.getBytes()得到UTF-8编码字节C2 A1而非十六进制解析的A1。解决必须用parseHexStrToByte(A1B2)等工具方法解析十六进制字符串禁用String.getBytes()。5.4 现象BLOB写入后DBMS_LOB.GETLENGTH()返回0但SELECT blob_col FROM t在SQL*Plus中显示(BLOB)原因未对BLOB locator执行DBMS_LOB.WRITE或WRITEAPPEND只插入了EMPTY_BLOB()locator为空。解决确认PL/SQL块中有DBMS_LOB.WRITEAPPEND调用JDBC中确认setBinaryStream()后调用了executeUpdate()。5.5 现象前端下载BLOB文件打开提示“文件已损坏”但用DBMS_LOB.SUBSTR()查前100字节显示正常原因JDBC读取时用了getBlob().getBytes()导致大文件被截断JDBC驱动有内部缓冲限制或HTTP响应头Content-Length未正确设置。解决强制用getBinaryStream()流式读取后端计算BLOB长度SELECT DBMS_LOB.GETLENGTH(blob_col) FROM t并设Content-Length响应头。6. 进阶技巧用DBMS_LOB包做BLOB内容校验与分块迁移当你要验证BLOB内容是否完整或把老系统LONG字段迁移到BLOB时DBMS_LOB包的底层能力就派上用场了。这里不讲理论只给两个能直接抄的脚本一个是校验BLOB的MD5避免传输损坏一个是LONG到BLOB的原子迁移不锁表、不丢数据。6.1 校验BLOB完整性用DBMS_CRYPTO生成MD5比应用层更可靠-- 创建函数返回BLOB的MD5哈希值16进制字符串 CREATE OR REPLACE FUNCTION blob_md5(p_blob IN BLOB) RETURN VARCHAR2 IS l_hash RAW(16); BEGIN IF p_blob IS NULL THEN RETURN NULL; END IF; l_hash : DBMS_CRYPTO.HASH(p_blob, DBMS_CRYPTO.HASH_MD5); RETURN RAWTOHEX(l_hash); END; / -- 使用SELECT id, blob_md5(blob_col) AS md5_hash FROM your_table WHERE id 1; -- 对比应用层计算的MD5若一致则BLOB未损坏为什么比Java校验强DBMS_CRYPTO.HASH在数据库服务端计算避免网络传输中的字节丢失RAWTOHEX输出标准大写十六进制与JavaMessageDigest结果完全一致。注意DBMS_CRYPTO需EXECUTE权限生产环境需DBA授权。6.2 Long到Blob原子迁移用DBMS_LOB.CREATETEMPORARY避免锁表-- 场景将表t_old的long_col迁移到t_new的blob_col要求不停服 DECLARE l_blob BLOB; l_long LONG; BEGIN -- 1. 创建临时BLOB不占用表空间会话级 DBMS_LOB.CREATETEMPORARY(l_blob, TRUE); -- 2. 逐行读LONG写入临时BLOB FOR r IN (SELECT id, long_col FROM t_old WHERE ROWNUM 1000) LOOP -- 将LONG转为RAW再写入BLOB规避LONG限制 DBMS_LOB.WRITEAPPEND(l_blob, LENGTH(r.long_col), UTL_RAW.CAST_TO_RAW(r.long_col)); -- 3. 插入新表BLOB列存临时BLOB INSERT INTO t_new (id, blob_col) VALUES (r.id, l_blob); -- 4. 重置临时BLOB供下一行使用 DBMS_LOB.TRIM(l_blob, 0); END LOOP; -- 5. 提交临时BLOB自动释放 COMMIT; END; /关键设计DBMS_LOB.CREATETEMPORARY创建会话级临时LOB不锁源表DBMS_LOB.TRIM(l_blob, 0)清空内容复用避免反复创建ROWNUM 1000分批处理防内存溢出。迁移后用blob_md5()校验一致性。6.3 一个我坚持十年的习惯所有LOB操作必加超时与重试在生产环境DBMS_LOB操作可能因LOB段争用而卡住尤其高并发写BLOB。我的做法是在PL/SQL中封装带超时的写入-- 封装函数带超时的BLOB写入 CREATE OR REPLACE FUNCTION safe_blob_write( p_blob IN OUT BLOB, p_buffer IN RAW, p_timeout_sec IN NUMBER DEFAULT 30 ) RETURN BOOLEAN IS l_start_time NUMBER : DBMS_UTILITY.GET_TIME; BEGIN WHILE DBMS_UTILITY.GET_TIME - l_start_time p_timeout_sec * 100 LOOP BEGIN DBMS_LOB.WRITEAPPEND(p_blob, UTL_RAW.LENGTH(p_buffer), p_buffer); RETURN TRUE; EXCEPTION WHEN OTHERS THEN IF SQLCODE -30036 THEN -- ORA-30036: unable to extend segment DBMS_LOCK.SLEEP(0.1); -- 等待100ms重试 CONTINUE; ELSE RAISE; END IF; END; END LOOP; RETURN FALSE; -- 超时 END; /为什么有效ORA-30036是LOB段空间争用典型错误DBMS_LOCK.SLEEP()让出CPU避免死循环DBMS_UTILITY.GET_TIME精度为百分之一秒p_timeout_sec * 100实现秒级超时。这个习惯让我在某次千万级BLOB导入中避免了3次服务中断。希望帮到你。本文还有配套的精品资源点击获取