Oracle 11g 的 Golang GORM 分页兼容解决方案

更新日期: 2026-01-15 阅读次数: 25 字数: 723 分类: oracle

参考前文,Ubuntu 安装 Oracle Instant Client, 并测试 Golang Gorm 读取 Oracle 数据库 搞定了 Oracle Instant Client 和 GORM 连接 Oracle 数据库的环境后,我以为马上就能开始写逻辑了。但是,测试了一下,发现了一个严重的问题。GORM 默认的实现方案,并不支持低版本 Oracle 的分页语法,准确的说是不支持 Oracle 11g 及更低版本。

FETCH NEXT xx ROWS ONLY

在 admin 管理后台,实现分页查询是非常常见的需求。但是,如果使用 GORM 的分页语法:

db.Select("*").
	Limit(limit).
	Offset((page - 1) * limit).
	Find(&items)

会报错:

dpiStmt_execute: ORA-00933: SQL command not properly ended
[1.344ms] [rows:0] SELECT * FROM "xxx"."some_table" ORDER BY id desc FETCH NEXT 20 ROWS ONLY

FETCH NEXT 20 ROWS ONLY 是 Oracle 12c 及更高版本才引入的语法。Oracle 11g,无法识别这段代码,从而报出 ORA-00933。

我尝试了让 AI 写一个分页查询的 SQL,我发现太麻烦了,而且可读性也很差。我感觉还是得用 ORM 框架来做。

纯 Golang 实现的 godoes/gorm-oracle

https://github.com/godoes/gorm-oracle

这个库文档里明确说明了支持 Oracle 11g。可以看到配置里有个专门的配置:

// RowNumberAliasForOracle11 is the alias for ROW_NUMBER() in Oracle 11g,
// defaulting to ROW_NUM
RowNumberAliasForOracle11: "ROW_NUM",

看了一下 github 上的代码实现:

https://github.com/godoes/gorm-oracle/blob/main/oracle.go#L290

里面的 RewriteLimit11 方法,重写了 GORM 的分页语法,改成了 Oracle 11g 支持的分页语法。

// RewriteLimit11 rewrite the LIMIT clause in the query to accommodate pagination requirements for Oracle 11g and lower database versions
//
// # Limit and Offset
//
//	SELECT * FROM (SELECT T.*, ROW_NUMBER() OVER (ORDER BY column) AS ROW_NUM FROM table_name T)
//	WHERE ROW_NUM BETWEEN offset+1 AND offset+limit
//
// # Only Limit
//
//	SELECT * FROM table_name WHERE ROWNUM <= limit ORDER BY column
//
// # Only Offset
//
//	SELECT * FROM table_name WHERE ROWNUM > offset ORDER BY column
func (d Dialector) RewriteLimit11(c clause.Clause, builder clause.Builder) {
	limit, ok := c.Expression.(clause.Limit)
	if !ok {
		return
	}
	offsetRows := limit.Offset
	hasOffset := offsetRows > 0
	limitRows, hasLimit := d.getLimitRows(limit)
	if !hasOffset && !hasLimit {
		return
	}

	var stmt *gorm.Statement
	if stmt, ok = builder.(*gorm.Statement); !ok {
		return
	}

	if hasLimit && hasOffset {
		// 使用 ROW_NUMBER() 和子查询实现分页查询
		if d.RowNumberAliasForOracle11 == "" {
			d.RowNumberAliasForOracle11 = "ROW_NUM"
		}
		subQuerySQL := fmt.Sprintf(
			"SELECT * FROM (SELECT T.*, ROW_NUMBER() OVER (ORDER BY %s) AS %s FROM (%s) T) WHERE %s BETWEEN %d AND %d",
			d.getOrderByColumns(stmt),
			d.RowNumberAliasForOracle11,
			strings.TrimSpace(stmt.SQL.String()),
			d.RowNumberAliasForOracle11,
			offsetRows+1,
			offsetRows+limitRows,
		)
		stmt.SQL.Reset()
		stmt.SQL.WriteString(subQuerySQL)
	} else if hasLimit {
		// 只有 Limit 的情况
		d.rewriteRownumStmt(stmt, builder, " <= ", limitRows)
	} else {
		// 只有 Offset 的情况
		d.rewriteRownumStmt(stmt, builder, " > ", offsetRows)
	}
}

测试了一下,果然可以正常分页查询了!

而 gorm 默认的 Oracle 驱动,是不支持 Oracle 11g 的分页语法的:

github.com/oracle-samples/gorm-oracle

从这个山寨的名字就感觉不太靠谱。

大小写问题

在 Oracle 中,当你给标识符(表名、列名、Schema 名)加上双引号时,Oracle 会变得严格区分大小写。因为 GORM 默认会给表名和列名加上双引号,所以你会遇到下面的问题:

dpiStmt_execute: ORA-00942: table or view does not exist

[14.539ms] [rows:0] SELECT count(*) FROM "some_schema"."some_table"

解决方法就是,把表名和 Schema 名都改成大写。

Oracle 11g 是哪年发布的?

Oracle 11g 于 2007 年发布,我也是服了,公司买这套 MES 系统,也是祖传架构了。。。

关于作者 🌱

我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式