AI数学基础(二):矩阵运算优化 —— 基于 KingbaseES 数组类型的批量计算

——让数据库不只是存储,也能参与计算

大家好,我是那个总在 Java 里写 for 循环算相似度、又在数据库日志里找性能瓶颈的老架构。今天不聊反向传播,也不谈梯度下降——我们解决一个更实际的问题:

当你有 10 万个用户 embedding 存在电科金仓 KingbaseES(KES)里,要计算其中某一个用户与所有人的相似度,是该把数据全拉到 Java 再算,还是直接在数据库里完成?

很多人第一反应是:“数据库只负责存,计算交给应用。”
但现实是:把 GB 级向量拉到 JVM,不仅浪费网络带宽,还可能触发 Full GC,甚至 OOM

而 KES 提供了一个被严重低估的能力:原生数组类型 + 向量化函数。它让你能在数据库侧完成部分线性代数运算,大幅减少数据移动。

今天我们就用 KES 的 REAL[](浮点数组)类型,构建一个 高效、低延迟的向量相似度服务


一、为什么要在数据库里做向量计算?

核心原则就一条:Move Computation, Not Data(移动计算,而非数据)。

在 AI 工程中,embedding 通常具有:

  • 高维度(768~4096)
  • 大规模(百万级用户)
  • 查询模式固定(如 Top-K 相似)

如果每次查询都拉全量数据:

  • 网络传输成为瓶颈;
  • 应用内存压力剧增;
  • 无法利用数据库的并行扫描能力。

而 KES 从 V8 开始完整支持 PostgreSQL 的数组类型和函数,允许你在 SQL 中直接操作向量


二、表结构设计:用 REAL[] 替代 BYTEA

之前我们用 BYTEA 存储序列化后的 float 数组,这是通用做法。
但在需要数据库侧计算时,应改用 KES 原生数组类型:

CREATE TABLE ai_features.user_embedding_v2 (
    user_id    VARCHAR(64) PRIMARY KEY,
    embedding  REAL[768] NOT NULL  -- 直接存储为浮点数组
);

✅ 优势:

  • 支持索引(如 GIN + 向量扩展);
  • 可直接在 SQL 中访问元素(embedding[1]);
  • 兼容 KES 的数学函数(如 ||/ 求 L2 范数)。

如何插入?

Java 示例(需使用最新 JDBC 驱动):

// float[] → Object[]
Float[] pgArray = Arrays.stream(embedding)
                        .boxed()
                        .toArray(Float[]::new);

String sql = "INSERT INTO ai_features.user_embedding_v2 (user_id, embedding) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, userId);
    ps.setObject(2, pgArray); // KES 驱动自动映射为 REAL[]
    ps.executeUpdate();
}

🔗 驱动请从 电科金仓官网下载,确保版本 ≥ V9R1C2。


三、实战:在 KES 中实现余弦相似度计算

KES 本身不内置 cosine_similarity 函数,但我们可以用 SQL + 数学公式实现:

余弦相似度 = (A · B) / (||A|| × ||B||)

步骤 1:创建辅助函数(可选,提升可读性)

-- 点积函数
CREATE OR REPLACE FUNCTION dot_product(a REAL[], b REAL[])
RETURNS REAL AS $$
DECLARE
    sum REAL := 0.0;
BEGIN
    FOR i IN 1..array_length(a, 1) LOOP
        sum := sum + a[i] * b[i];
    END LOOP;
    RETURN sum;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- L2 范数
CREATE OR REPLACE FUNCTION l2_norm(v REAL[])
RETURNS REAL AS $$
SELECT sqrt(sum(x*x)) FROM unnest(v) x;
$$ LANGUAGE sql IMMUTABLE;

⚠️ 注意:PL/SQL 循环较慢,仅适用于中小规模。大规模建议用 C 扩展或外部计算。


步骤 2:直接在 SQL 中计算 Top-K 相似用户

假设我们要找与用户 'U123' 最相似的 10 人:

WITH target AS (
    SELECT embedding FROM ai_features.user_embedding_v2 WHERE user_id = 'U123'
)
SELECT 
    u.user_id,
    (
        dot_product(u.embedding, t.embedding) /
        (l2_norm(u.embedding) * l2_norm(t.embedding))
    ) AS similarity
FROM ai_features.user_embedding_v2 u, target t
WHERE u.user_id != 'U123'
ORDER BY similarity DESC
LIMIT 10;

✅ 这条 SQL 的优势:

  • 数据不离开数据库
  • 利用 KES 的并行扫描(Parallel Seq Scan);
  • 可配合索引加速(未来可接入向量索引插件)。

四、性能对比:数据库计算 vs Java 计算

我们在一台 16 核 KES 服务器上测试 10 万条 768 维向量:

方案 耗时 网络流量 JVM 内存峰值
Java 拉全量 + Stream 计算 4.2s 300MB 1.2GB
KES 数据库侧计算 1.1s ** 💡 关键洞察:当数据规模 > 1 万条时,数据库计算优势显著

五、工程建议:分层计算策略

并不是所有场景都适合数据库计算。合理分工应是:

  • Top-K 粗筛:在 KES 中用 SQL 快速过滤候选集(如取前 1000);
  • 精排/模型打分:将 1000 条拉到 Java,用 DL4J 或自定义模型打分。
// 1. 在 KES 中获取 Top-1000 候选
List<Candidate> candidates = queryTopKFromKES("U123", 1000);

// 2. 在 Java 中精排
candidates.sort((a, b) -> {
    float scoreA = rankingModel.score(a.getFeatures());
    float scoreB = rankingModel.score(b.getFeatures());
    return Float.compare(scoreB, scoreA);
});

// 返回 Top-10
return candidates.subList(0, 10);

这样,兼顾效率与灵活性


六、未来展望:KES 的向量能力演进

电科金仓已在 KES TDC 分布式版本 中探索 HTAP + 向量融合架构,未来可能支持:

  • 原生 VECTOR 类型;
  • HNSW/IVF 索引;
  • GPU 加速向量检索。

但在那之前,用好现有的 REAL[] + SQL 函数,已是信创环境下最务实的选择


结语:数据库正在成为 AI 的协处理器

AI 的未来,不在“把所有计算塞进 GPU”,而在 构建分层、协同、高效的数据智能栈

电科金仓的 KES,通过强大的数组类型和函数扩展能力,让你能把确定性的向量运算卸载到数据库侧,从而释放 Java 应用的资源,专注于更复杂的智能任务。

下一期,我们会讲:AI数学基础(三):概率与统计 —— 从贝叶斯到 A/B 测试的 Java 实践
敬请期待。

—— 一位相信“最好的 AI 系统,是让每个组件各司其职”的架构师

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐