news 2026/9/18 9:45:32

Java处理Oracle Clob字段:读写方案、框架映射与异常排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java处理Oracle Clob字段:读写方案、框架映射与异常排查指南

前两天帮同事排查一个数据同步任务,同步到一半抛了个异常,日志里清清楚楚写着“目标缓冲区太小,无法容纳字符集转换之后的Clob数据”。当时一看就知道又是Java处理Oracle Clob字段的老问题:没搞清楚Clob和普通字符串的区别,直接在JDBC里当String读,结果CLOB内容一长或者字符集一转换,缓冲区就被打爆。这类问题看着不大,但只要你做过Java连Oracle的开发,多半迟早会踩上一次。

所以这篇就专门聊聊Java怎么处理Oracle的Clob类型字段:怎么读、怎么写、怎么在MyBatis和JPA里映射、遇到“目标缓冲区太小”这类报错怎么排查。内容不绕弯子,全是我实际项目中用过的方案和踩坑记录,适合正在被Clob折磨的Java开发,尤其是做数据迁移、报表导出、接口对接和内容管理系统的人。看完你至少能直接抄一套读写工具类,并且知道常见的坑在哪。

1. 先说清楚:Oracle里的Clob到底是个啥

1.1 Clob和Varchar2、Long的差别

Oracle里存字符串,很多人第一反应就是Varchar2。但Varchar2在标准用法下最多存4000字节,12c以后虽然有扩展选项可以到32767字节,可依然有长度上限。如果你要存几十万字的那种文章正文、接口报文、JSON配置文件,Varchar2肯定不够用,这时候就得请出Clob。

Clob全称Character Large Object,是Oracle内置的大对象类型,专门用来存字符类型的大数据。它能存多大?理论极限是4GB,实际受数据库块大小和表空间限制,但对绝大多数业务来说,存个几百万字符的文本绰绰有余。与之相关还有Blob,存二进制数据,图片、文件流走Blob;NClob专门存Unicode字符数据。另外还有一个已经淘汰的Long类型,老项目里偶尔能见到,Oracle官方早就建议把Long迁移到Clob了。

从使用习惯上讲,Clob不能像Varchar2那样直接参与等值比较、排序、分组,也不能随便建普通索引(要建索引也得用函数索引配合SUBSTR或者Oracle Text)。所以表设计时,能用Varchar2解决的别泛用Clob,只有确认要存大文本才用Clob。

1.2 你会在哪些场景遇到Clob

我见过最常见的Clob应用场景有这么几类:

  • 文章编辑器保存正文,尤其是富文本编辑器生成的HTML片段,一篇几千上万字很正常,Varchar2很难放下。
  • 接口报文存储,比如对接第三方支付、物流回调,把原始Request和Response存下来排查问题,报文动辄几十KB。
  • 日志明细表,例如同步任务执行日志、导出任务日志,把每次执行的详细异常堆栈放进Clob字段。
  • 业务系统里的长文本配置,比如邮件模板、短信模板、合同内容,不定长且可能很大。
  • 数据库表字段从Long改造迁移到Clob,很多老系统的备注字段就是这么改的。

遇到这些场景,代码里就不能只想着rs.getString("content")一把梭了。尤其是CLOB超过几万字符时,直接当String取回来很容易触发字符集相关的怪问题。

1.3 JDBC里Clob对应的Java类型

在JDBC层面,Clob对应的是java.sql.Clob接口,Oracle驱动提供了实现类oracle.sql.CLOB。很多新手不知道这一点,看到CLOB列就习惯性地调ResultSet.getString()。其实getString()在字段值不是太大时确实能拿到结果,Oracle驱动内部会帮我们做转换,但这个转换过程是有代价的,遇到长文本或字符集不一致就可能翻车。

正确理解应该是这样:

  • 你从ResultSet拿到的可以是一个Clob实例,通过clob.getSubString(1, (int) clob.length())再转String;
  • 也可以直接getCharacterStream()拿 Reader,分块读;
  • 也可以getAsciiStream()拿 InputStream,适合纯ASCII内容;
  • 还可以在SQL查询层面用DBMS_LOB.SUBSTRTO_CHAR把Clob提前转成字符串,但要注意长度限制。

框架层面,MyBatis和Hibernate都有对应的TypeHandler和类型映射来处理Clob,后面章节会讲,但底层逻辑始终离不开上面这几种读写方式。

2. Java读取Clob的几种正确姿势

2.1 最快入门:getClob加getSubString

如果你是第一次处理Clob,最简单不出错的读法就是通过getClob拿到Clob对象,再调用getSubString把它转成String。代码长这样:

try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("select content from t_article where id = ?")) { ps.setLong(1, articleId); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Clob clob = rs.getClob("content"); if (clob != null) { String content = clob.getSubString(1, (int) clob.length()); System.out.println(content); } } } }

这段代码对大多数几千到几万字符的场景足够用了。需要注意的是clob.length()返回的是long,如果CLOB内容超过Integer.MAX_VALUE,直接强转int会溢出,此时必须用流式读取,不能一下子搬进数组。虽然实际业务里超过20亿字符的极少,但严谨起见,我一般会判断一下长度。

还有个细节:拿到Clob后用完要不要释放?ResultSet.close()会释放它,不过为了保险,长事务里最好调用一下clob.free(),这是JDBC 4.0提供的方法,可以提前释放CLOB持有的数据库资源。

2.2 稳妥的大字段读取:CharacterStream

实测下来,读取超过几十万字符的CLOB时,getSubString一次性拿全量依然有内存风险。更稳的方案是用getCharacterStream(),按缓冲区一截一截读。这样内存占用固定,不管CLOB多大都能读,而且还能避开不少字符集转换问题。

try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("select content from t_log_detail where id = ?")) { ps.setLong(1, logId); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Clob clob = rs.getClob("content"); if (clob != null) { StringBuilder sb = new StringBuilder(); try (Reader reader = clob.getCharacterStream()) { char[] buffer = new char[8192]; int len; while ((len = reader.read(buffer)) != -1) { sb.append(buffer, 0, len); } } String content = sb.toString(); // 处理content } } } }

char[] buffer设多大的?8KB到16KB比较合适,太小循环次数多,太大浪费内存。这种读法其实也更符合“JDBC流式读取”的初衷。我的经验是,只要CLOB可能超过1MB,就无脑用流,不要犹豫。

2.3 封装一个Clob转String的工具方法

每次写getCharacterStream那段代码很烦,我习惯把Clob转String的逻辑封装成静态方法,放到项目的JdbcUtils里。核心逻辑就三中情况:null返回空串;长度不大的用getSubString;长度大的用Reader缓冲读。同时加一层异常转换,让调用方不用处理SQLException

public static String clobToString(Clob clob) throws Exception { if (clob == null) { return ""; } long len = clob.length(); if (len == 0) { return ""; } if (len <= Integer.MAX_VALUE && len <= 1024 * 1024) { return clob.getSubString(1, (int) len); } StringBuilder sb = new StringBuilder(); try (Reader reader = clob.getCharacterStream()) { char[] buffer = new char[8192]; int n; while ((n = reader.read(buffer)) != -1) { sb.append(buffer, 0, n); } } return sb.toString(); }

这样代码里一行ClobUtil.toString(rs.getClob("content"))就完事了。工具层别的不用管,专一干翻译的活,好用。

2.4 查询层面直接to_char的取舍

也有一种路子,写SQL时直接用TO_CHAR(content)或者DBMS_LOB.SUBSTR(content, 4000, 1)把Clob转成字符串返回,然后Java里再正常getString。这种方式适合只取前N个字符做列表摘要的场景,因为TO_CHAR对CLOB的转换结果有长度限制,最稳妥的DBMS_LOB.SUBSTR也只能截取最多32767字节,而且参数传递的是字节数,不是字符数,中文场景很容易截出半个字。

所以我的结论是:如果你只需要CLOB前几百个字符,放在SQL里用DBMS_LOB.SUBSTR(content, 500, 1)截取当然方便;如果必须取全量,别在SQL层做,老老实实把Clob对象取回来用流读。别图一时清爽,回头被截断问题反咬一口。

3. Java写入Clob:别再把String硬塞给setObject

3.1 setClob和setString的区别

写入CLOB时,不少新人会写ps.setString(1, content),这个在内容长度不超过数据库限制时,Oracle驱动通常会帮你自动转成CLOB,所以能跑通。可一旦内容很大,或者驱动版本比较老,就容易报ORA-01461: 仅能绑定要插入LONG列的LONG值。这个错的意思就是:你绑定了一个超长的字符串,Oracle驱动不知道该怎么帮你转,直接把它当成了LONG值处理。

更稳妥的办法是使用PreparedStatement.setClob(int, Clob),或者直接给驱动提供java.sql.Clob实现。但手动构造Clob实现比较繁琐,通常我们用Oracle的oracle.sql.CLOB,或者用连接创建Clob

Clob clob = connection.createClob(); clob.setString(1, content); ps.setClob(1, clob);

Connection.createClob()是JDBC标准接口,不会绑定到Oracle私有类,推荐优先用。如果内容太大,同样可以clob.setCharacterStream(1)拿到Writer再分块写。

3.2 正确的临时Clob写入流程

还有一类更常见的业务:插入一条记录时,CLOB字段先给个empty_clob()占位,然后再通过UPDATE把内容写进去。这个流程主要用来处理需要传文件流或者CLOB内容特别大的场景,预先获取一个可写的Clob句柄。

第一步,插入时:

insert into t_article (id, title, content) values (1, '标题', empty_clob());

第二步,查询这条记录并加上FOR UPDATE锁住:

String sql = "select content from t_article where id = ? for update"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setLong(1, 1L); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Clob clob = rs.getClob("content"); clob.setString(1, bigContent); } } } conn.commit();

注意必须用FOR UPDATE,否则你拿到Clob对象后去setString,很可能抛“无法更新不同事务/结果集”之类的错。clob.setString(1, bigContent)这个方法也有限制,如果内容特别大,最好用clob.setCharacterStream(1)拿到Writer分块写:

try (Writer writer = clob.setCharacterStream(1L)) { writer.write(bigContent); }

写完后一定记得提交事务。我最早踩过坑:写完没commit,结果数据一直查不到,还以为是update没生效。

3.3 批量更新Clob时的注意事项

批量更新多个CLOB字段,很多人试图在for循环里反复执行INSERT ... empty_clob()SELECT ... FOR UPDATE再更新,性能非常差,因为每条记录都要往返多次。更好的做法是压缩交互次数,能合并SQL就合并,不能合并就分批提交。

如果业务允许,可以考虑把内容先组装成字符串,集中在同一个事务里处理,每处理100条记录提交一次。另外,单条CLOB内容很大时,网上有经验说setString写入很慢,但实测下来瓶颈一般在网络传输和数据库日志写入,Java侧只需要确保使用流式写入,别把10MB字符串再复制成多份。

还有个小坑:CLOB字段参与事务时会占用undo空间,大批量写入时千万记得分批提交,别一个大事务几十万条,最后undo扩展疯了,迪斯科都救不了你。

4. 实体映射与主流框架的Clob处理

4.1 MyBatis如何处理Clob

用MyBatis的时候,实体类里直接写private String content;,对应表的CLOB字段。默认情况下,MyBatis的StringTypeHandler会尝试把CLOB转成String,大部分场景能用,但遇到大CLOB或字符集问题同样会翻车。

稳妥的做法是自定义一个ClobTypeHandler,继承BaseTypeHandler<String>,在getNullableResult里用流式方式读取CLOB,在setNonNullParameter里用connection.createClob()写入。代码不算多,我贴过很多次:

@MappedTypes(String.class) public class ClobTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { Clob clob = ps.getConnection().createClob(); clob.setString(1, parameter); ps.setClob(i, clob); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return toString(rs.getClob(columnName)); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return toString(rs.getClob(columnIndex)); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return toString(cs.getClob(columnIndex)); } private String toString(Clob clob) throws SQLException { if (clob == null) { return null; } long len = clob.length(); if (len == 0) { return ""; } if (len <= Integer.MAX_VALUE && len <= 1024 * 1024) { return clob.getSubString(1, (int) len); } StringBuilder sb = new StringBuilder(); try (Reader reader = clob.getCharacterStream()) { char[] buffer = new char[8192]; int n; while ((n = reader.read(buffer)) != -1) { sb.append(buffer, 0, n); } } catch (IOException e) { throw new SQLException("读取CLOB失败", e); } return sb.toString(); } }

然后在Mapper.xml里指定:

<resultMap id="ArticleResultMap" type="Article"> <id property="id" column="id"/> <result property="content" column="content" typeHandler="com.example.handler.ClobTypeHandler"/> </resultMap>

插入时也写上typeHandler,让MyBatis用我们自己的逻辑写入。这么折腾一次,大文本读写基本就稳了。

4.2 JPA/Hibernate里的@Lob

JPA和Hibernate处理Clob相对简单,实体字段上标注@Lob,然后声明列类型是CLOB:

@Entity @Table(name = "t_article") public class Article { @Id private Long id; @Lob @Column(columnDefinition = "CLOB") private String content; }

Hibernate会自动把Java String和Oracle CLOB之间做转换。老版本Hibernate在Oracle上有时会映射成LONG,所以columnDefinition = "CLOB"最好显式写上。如果你用的是Hibernate 6.x,String字段加@Lob映射CLOB问题不大,但如果你在PostgreSQL和Oracle之间切换,columnDefinition就需要注意兼容性。

4.3 常见的映射踩坑:懒加载与序列化

使用ORM框架时,CLOB字段还会带来两个隐蔽问题。

第一个是懒加载。JPA里如果你的实体上有@Basic(fetch = FetchType.LAZY),CLOB字段可能不会立即加载,事务一旦关闭再访问该字段就报LazyInitializationException。这时候要么改成EAGER,要么在设计查询时使用@EntityGraph显式fetch,要么在事务内完成访问。别在Controller层直接返回实体,很多坑就是这么来的。

第二个是序列化。一个包含CLOB字段的实体,如果直接用Jackson转JSON,转出来的内容没问题,但转JSON的过程会触发CLOB读取,相当于在Controller层访问数据库大字段,容易造成性能瓶颈。建议写一个DTO,只映射需要的字段,CLOB场景下尤其重要。我见过同事把整篇10万字的文章正文通过实体直接返回给前端,接口响应直接起飞。

5. 高频报错“目标缓冲区太小,无法容纳字符集转换之后的Clob数据”怎么破

5.1 先搞清楚这个报错哪里来的

这个报错我最早遇到是在用JDBC直接rs.getString()读CLOB列时。Oracle JDBC驱动在读取CLOB类型时,需要把数据库里的字符序列转换成Java侧的字符序列,转换过程会经过一个内部缓冲区。如果缓冲区容量不够,比如客户端字符集和数据库字符集不一致,或者CLOB内容比较大,驱动就会抛出“目标缓冲区太小,无法容纳字符集转换之后的Clob数据”。

网上很多人搜这个报错,大概率是配合“executeQuery异常”或者“读取CLOB时报错”一起发生的。你可以把它理解成Oracle JDBC内部的一次“长度校验失败”,不是SQL语法错误,也不是权限问题,就是读取大文本的姿势不对,或者环境字符集不匹配。

5.2 临时解决办法:换一种读取方式

遇到这个报错,立刻把代码从rs.getString("content")改成流式读取,大部分情况下能快速绕过。

Clob clob = rs.getClob("content"); Reader reader = clob.getCharacterStream(); // 分块读取

因为getCharacterStream不会强求一次性把所有内容塞进目标缓冲区,是多少就读多少,所以能有效规避这个报错。如果还报错,再检查一下是不是驱动版本太老,把ojdbc6换成ojdbc8或者厂商最新JDBC驱动,这类底层bug在新驱动里修复了不少。

如果你的代码不是自己写的,而是某个框架内部在读CLOB,那就看框架有没有提供自定义TypeHandler的口子,有的话换成上面那种流式处理的Handler,没有的话就得考虑SQL层面截断或者分页取数了。

5.3 从根上排查:NLS_LANG和JDBC连接属性

临时绕过去之后,麻烦的原因还在。Oracle的字符集转换机制非常看重客户端和数据库端的字符集设置。字符集不一致时,中文字符占用的字节数不同,转换缓冲区就更容易爆掉

排查步骤可以这样:

  1. 在数据库执行:select userenv('language') from dual;,看看数据库端字符集是什么,常见的是ZHS16GBKAL32UTF8
  2. 检查应用启动时的JVM编码,例如-Dfile.encoding=UTF-8
  3. 检查Oracle客户端的NLS_LANG环境变量。统一改成和数据库端一致,比如数据库是AMERICAN_AMERICA.ZHS16GBK,那NLS_LANG也设为AMERICAN_AMERICA.ZHS16GBK
  4. JDBC连接串里尽量不要人为指定奇怪的字符集参数,oracle.jdbc.convertUTF8这类属性按官方文档设置,别乱加。

最根本的方向:让数据库字符集和应用使用的字符集尽量匹配,推荐统一使用AL32UTF8,这也是Oracle新系统的标准配置。老系统如果已经是GBK,那就老老实实让NLS_LANG跟着GBK走。

5.4 一段可以用于复现的最小代码

这个报错的复现也不是每次都能稳定出现,但通过强制读取超长中文CLOB、并把客户端NLS_LANG设置成不一致,复现概率很高。我简单写一个复现思路:

// 假设数据库中CLOB内容是一段超过10万字符的中文 String sql = "select content from t_big_clob where id = 100"; stmt.setFetchSize(1); try (ResultSet rs = stmt.executeQuery(sql)) { rs.next(); String content = rs.getString(1); // 高概率触发目标缓冲区太小 }

setFetchSize(1)降低预取行数,加上较大的CLOB,一旦字符集配置不当,报错就出现了。如果你现在还没遇到这个错,恭喜,说明你的环境还算健康;遇到了也别慌,按上面两步处理。

6. 实操:一个完整的Clob读写工具类与测试

6.1 工具类代码结构

为了让你能直接抄,我把项目里最常用的CLOB工具类简化整理出来,涵盖读、写、流式三部分。整体设计以java.sql.Clob为标准接口,不依赖Oracle私有类,所以换数据库理论也能用。

public final class ClobUtil { private ClobUtil() { } /** * Clob转String,自动判断是否走流式 */ public static String toString(Clob clob) throws SQLException { if (clob == null) { return null; } long len = clob.length(); if (len == 0) { return ""; } if (len > Integer.MAX_VALUE) { return streamToString(clob); } if (len <= 1024 * 1024) { return clob.getSubString(1, (int) len); } return streamToString(clob); } /** * 流式读取 */ public static String streamToString(Clob clob) throws SQLException { StringBuilder sb = new StringBuilder(); try (Reader reader = clob.getCharacterStream()) { char[] buffer = new char[8192]; int n; while ((n = reader.read(buffer)) != -1) { sb.append(buffer, 0, n); } } catch (IOException e) { throw new SQLException("读取CLOB失败", e); } return sb.toString(); } /** * 字符串写入Clob,优先使用Connection.createClob */ public static Clob stringToClob(Connection conn, String content) throws SQLException { Clob clob = conn.createClob(); clob.setString(1, content == null ? "" : content); return clob; } /** * 大字符串分块写入Clob */ public static void writeString(Clob clob, String content) throws SQLException, IOException { if (content == null) { return; } try (Writer writer = clob.setCharacterStream(1L)) { writer.write(content); } } }

这里面的核心判断是当CLOB长度大于1MB时走流式。这个阈值你可以根据项目调整,我一般习惯1MB,内存充足也可以放松,但不建议超过10MB才走流式。

6.2 配套建表SQL与测试逻辑

写一个最简测试用例,先建表:

create table t_big_clob ( id number primary key, content clob );

插入一条大文本记录:

try (Connection conn = dataSource.getConnection()) { String bigText = "测试数据".repeat(200000); // 约60万字符 String sql = "insert into t_big_clob(id, content) values (1, ?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { Clob clob = ClobUtil.stringToClob(conn, bigText); ps.setClob(1, clob); ps.executeUpdate(); } conn.commit(); }

读取时:

try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("select content from t_big_clob where id = 1")) { try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { String result = ClobUtil.toString(rs.getClob("content")); System.out.println("读取长度:" + result.length()); } } }

我本地跑过60万字符的CLOB,用流式读取耗时基本可以忽略,倒是写入时createClob()setString的路径容易因内容过大报错,所以返回Clob时建议优先使用工具类的stringToClob,不要直接clob.setString写入超大字符串。内容超过几MB时,更推荐先插入empty_clob(),再查出来用writeString分块写,对驱动更友好。

6.3 工具类使用时的边界问题

  • 如果CLOB列允许NULL,读取时务必先判空,否则clob.length()直接空指针。
  • 如果内容本身就是空串,getSubString(1, 0)可能报错,工具类里已经返回空字符串。
  • Connection.createClob()不是所有老驱动都支持,如果用的ojdbc6之前的驱动,需要手动构造oracle.sql.CLOB,但这类驱动已经太老了,能升级就升级。
  • Writer.write内部如果内容超过驱动单次处理的限制,可能需要循环写入,但实测标准JDBC实现里write(String)会把内容拆成内部块,不强制手动循环。

7. 常见问题排查速查表

7.1 问题现象、原因、解法对照

我把平时容易被Clob坑到的点整理成一个速查表,你遇到问题直接对着查:

问题现象根本原因解决方案
目标缓冲区太小,无法容纳字符集转换之后的Clob数据JDBC内部字符集转换缓冲区不足,或直接用getString读大CLOB改用getClob+getCharacterStream流式读取;检查NLS_LANG;升级驱动
ORA-01461: 仅能绑定要插入LONG列的LONG值用setString插入超大内容,驱动误判为LONG使用Connection.createClob()+setClob插入
读取CLOB内容乱码客户端字符集和数据库字符集不一致统一NLS_LANG和JVM编码,推荐AL32UTF8
Clob字段读取速度很慢每次读全文,网络和IO开销大只取摘要时用DBMS_LOB.SUBSTR;取全文时用流式并合理设置fetch size
MyBatis查询CLOB字段报错默认StringTypeHandler对大CLOB支持不佳自定义ClobTypeHandler覆盖读写
JPA查询CLOB字段后实体访问报LazyInitializationException懒加载字段在事务外访问调整fetch策略或在事务内组装DTO
Clob内容过长导致 out of memory一次性把超大CLOB转成String使用流式处理,或分段读取,避免全量加载
clob.setString写入大文本报错单次写入内容过大使用setCharacterStream分块写入,或先empty_clob()再更新

这张表基本上覆盖了我见过的90%的项目问题。

7.2 我的排查套路

如果你现在手里正有一条CLOB读写异常的报错,我建议按这个顺序走:

先看异常栈,判断是JDBC层还是ORM层报的。JDBC层报的优先检查代码里是不是用了getString直接读CLOB,先改成流式读法再跑一遍。

问题还在,就进入字符集检查。查数据库端字符集、客户端NLS_LANG、JVM默认编码是否一致。这一环往往才是根因,尤其项目部署在Linux服务器上,环境变量容易漏配。

再看驱动版本。太老的ojdbc版本处理CLOB和字符集转换的bug比较多,替换成官方较新版本,很多问题莫名其妙就好了。

最后如果代码、环境、驱动都没问题,就是数据本身有特殊情况,比如CLOB内容里包含特殊Unicode字符或非法XML字符,这种就需要单独清洗数据。

最后分享一个小经验

我处理CLOB字段最大的心得就是:哪怕业务里现在存的都是几千字的短文本,也不要养成rs.getString一把梭的习惯,流式读取其实并没有多写几行代码,但能避免很多未来才会爆发的坑。另外,所有涉及CLOB的SQL操作,最好在生产环境先执行一次真实数据量级的压测,不要拿测试库几条数据就以为万事大吉。CLOB这东西平时不声不响,等你线上数据起来再出问题,往往就是那种最难查的字符集和缓冲区问题。希望这篇经验总结能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 9:45:11

0.96寸OLED驱动全攻略:从IIC原理到STM32移植与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:45:04

衡重式路肩挡土墙计算:土压力与稳定验算的Python实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:42:47

RevokeMsgPatcher 防撤回补丁快速指南

RevokeMsgPatcher 防撤回补丁快速指南 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.com/GitHub_Trending/re/Revok…

作者头像 李华
网站建设 2026/9/18 9:41:52

微分平坦:让机械臂轨迹规划从高维求解到降维打击

做机械臂轨迹规划的人&#xff0c;应该都遇到过类似的痛点&#xff1a;给定一个末端位姿&#xff0c;你不仅要规划出一条不撞障碍物的路径&#xff0c;还得保证关节力矩不超过上限、加速度不猛冲、末端不抖。一旦把这些动力学约束全部塞进去&#xff0c;问题瞬间变成一个高维非…

作者头像 李华
网站建设 2026/9/18 9:41:34

MySQL锁机制全解析:从表锁行锁到死锁排查实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:41:33

从零实现桌面鱼缸 MiroFish:Boids 鱼群模拟与透明窗口性能优化

1. MiroFish 这个"看着没什么用"的项目&#xff0c;为什么值得认真做一遍第一次把 MiroFish 挂在桌面上&#xff0c;我盯着屏幕里那十几条鱼看了大概十分钟&#xff0c;然后才想起来自己本来是要去开会的。它就是这么一个东西&#xff1a;一个常驻在桌面上、没有边框…

作者头像 李华