# MySQL + Milvus = PostgreSQL?一张表搞定 AI Agent 的长时记忆(附完整代码)

MySQL + Milvus = PostgreSQL?一张表搞定 AI Agent 的长时记忆(附完整代码)

豆包、ChatGPT、Claude、Codex...... 这些 AI Agent 都有一个共同需求:长期记忆 。聊天记录不能只活在一次会话里,用户隔天回来,Agent 要能接着聊,更要能"回忆"起之前说过的话。本文从零开始,讲清楚如何用 PostgreSQL + pgvector 在一张表上同时搞定「传统关系查询 + AI 语义检索」,并给出 Node.js(pg 驱动) 与 NestJS + TypeORM 的完整工程代码,建议收藏后边看边练。

关系型数据库是互联网应用的基石------账号信息、订单数据、聊天记录、业务数据,几乎都靠它存储。而到了 AI 时代,这个"基石"要被重新审视了。


一、为什么说 MySQL + Milvus = PostgreSQL?

先看传统做法。要让 Agent 拥有"记忆",需要两类能力:

  1. 精确查询:按用户查会话列表、按会话查消息、按时间排序------这是关系型数据库的强项(MySQL)。
  2. 语义检索 :用户说"上次讨论的向量检索方案是什么",系统要能理解意图,在历史消息里做相似度搜索------这是向量数据库的强项(Milvus)。

于是常见架构是「MySQL 存数据 + Milvus 存向量」,这带来三个麻烦:

  • 双写:一条消息要写两个库,一致性靠业务代码保证,写挂了就是数据对不上;
  • 同步:MySQL 的数据要同步给 Milvus,要么定时任务,要么监听 binlog,多一套链路多一堆故障;
  • 两套系统:部署、扩容、监控、备份,成本翻倍。

PostgreSQL 的解法简单粗暴:在原来的消息表上多加一个向量字段。

维度 MySQL + Milvus 双写 PostgreSQL + pgvector
存储 两套系统 一张表
写入 双写 + 手动保证一致 单写
语义检索 单独查 Milvus SQL 里直接用 <=>
维护成本 高 低

不需要拆架构、不需要同步数据、不需要写复杂的关联逻辑------一张表,搞定传统关系查询 + AI 长期记忆。这就是 AI 时代 PostgreSQL 的核心优势。


二、表结构设计:用户 → 会话 → 消息

Agent 的聊天记忆,三张表就够了,关系非常典型:

text 复制代码
users (用户表) 1 ────< conversations (会话表) 1 ────< messages (消息表)
                   一对多                        一对多
  • users :用户表,核心字段 user_id(自增主键);
  • conversations :会话表,通过 user_id 关联用户表,一条用户记录对应多条会话(一对多);取会话列表就是按 user_id 过滤;
  • messages :消息表,通过 conversation_id 关联会话表;点击某个会话,就取出当前会话的全部消息。

DDL 如下(注意消息表多了一个 embedding 向量字段,这是 AI 记忆的关键):

sql 复制代码
-- 1. 启用 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;

-- 2. 用户表
CREATE TABLE users (
  id         BIGSERIAL PRIMARY KEY,
  name       TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- 3. 会话表(与用户一对多)
CREATE TABLE conversations (
  id         BIGSERIAL PRIMARY KEY,
  user_id    BIGINT NOT NULL REFERENCES users(id),
  title      TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- 4. 消息表(多一个向量字段 embedding)
CREATE TABLE messages (
  id              BIGSERIAL PRIMARY KEY,
  conversation_id BIGINT NOT NULL REFERENCES conversations(id),
  role            TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
  content         TEXT NOT NULL,
  -- 向量字段:维度必须与 embedding 模型输出对齐(如 text-embedding-v3 默认 1024 维)
  embedding       vector(1024),
  created_at      TIMESTAMPTZ DEFAULT now()
);

注意:embedding 的维度必须和你用的 embedding 模型输出维度一致,写错了存不进去。这里的 vector(1024) 对应 text-embedding-v3 的默认维度。


三、最基础的 SQL:查会话、查消息

设计好了表,先做最传统的查询------这正是关系型数据库的本职。

某用户的全部会话列表(按创建时间倒序,最新的在最上面):

sql 复制代码
SELECT *
FROM conversations
WHERE user_id = '你的用户ID'
ORDER BY created_at DESC;

某会话的全部消息(按时间正序,还原对话顺序,给 LLM 当上下文):

sql 复制代码
SELECT *
FROM messages
WHERE conversation_id = '你的会话ID'
ORDER BY created_at ASC;

到这一步,MySQL、PostgreSQL 都能做,看不出差别。差别在下一节。


四、JOIN 复习:内连接与外连接

当查询要跨多张表时,就要用到 JOIN。这里顺带把四种连接复习一遍,后面语义检索的 SQL 会用到:

sql 复制代码
-- INNER JOIN:取交集,两边都有匹配才返回
SELECT m.*
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = '你的用户id' AND c.id = '你的会话ID'
ORDER BY m.created_at ASC;
连接类型 语义 特点
INNER JOIN 交集 两边都有匹配才返回
LEFT JOIN 左表为主 右表没有匹配则补 NULL
RIGHT JOIN 右表为主 左表没有匹配则补 NULL
FULL JOIN 并集 两边都保留,无匹配补 NULL

上面这条 SQL 的意义在于:先按用户过滤(会话属于谁),再按会话过滤(消息属于哪场对话),最后按时间排序------"按用户过滤、按会话过滤、按时间过滤",这就是 AI 时代消息系统最需要的能力。


五、升级:给消息表加一个向量字段(pgvector)

现在到了分水岭。Agent 的"记忆"不只是把消息存下来,还要能在历史里按语义找到相关内容------比如用户问"我之前提到的向量索引方案是什么",系统要检索出语义上最接近的消息,而不是靠关键词匹配。

这需要把每条消息转成**向量(embedding)**存进数据库。PostgreSQL 的解法:

sql 复制代码
-- 建好扩展后,直接给已有表加向量列
ALTER TABLE messages ADD COLUMN embedding vector(1024);

写入消息时,先用 embedding 模型(如 text-embedding-v3)把文本转成向量,连同消息一起写入:

js 复制代码
// 伪代码示意:真正实现见第七、九节的完整代码
const vector = await getEmbeddings().embedQuery(content);

await query(
  `INSERT INTO messages (conversation_id, role, content, embedding)
   VALUES ($1, $2, $3, $4::vector)`,
  [conversationId, role, content, JSON.stringify(vector)]
);

这里有两个关键点:

  1. $4::vector:把传入的第 4 个参数强制类型转换为 PostgreSQL 的 vector 向量类型;
  2. JSON.stringify(vector):向量数组以 JSON 字符串形式传给驱动,pgvector 会解析成真正的向量。

MySQL 原生不支持向量类型(虽有 HeatWave 等方案,但生态和成熟度远不如 PG 的 pgvector 扩展)。这就是"AI 时代,PG 优势更大"的原因------不需要额外引入向量数据库。


六、语义检索:<=> 余弦距离运算符

向量存进去了,怎么检索?pgvector 提供了一套向量运算操作符:

操作符 含义
<=> 余弦距离(cosine distance)
<-> 欧氏距离(L2)
<#> 内积(inner product)

其中 <=> 最常用,因为余弦距离对文本语义相似度最友好:

向量余弦距离 = 1 - 向量余弦相似度

距离越小,语义越接近。所以"找最相似的消息"就是:

sql 复制代码
SELECT id, conversation_id, role, content, created_at,
       1 - (embedding <=> '[1.2, 0.5, 0.8, ...]') AS similarity
FROM messages
WHERE conversation_id = '你的会话ID'
  AND embedding IS NOT NULL
ORDER BY embedding <=> '[1.2, 0.5, 0.8, ...]'   -- 按余弦距离升序 = 按相似度降序
LIMIT 5;

把这条 SQL 和第四节的 JOIN 合并,就是"AI 时代长时记忆"的完整形态------一次查询同时做到:按用户过滤 + 按会话过滤 + 按语义检索:

sql 复制代码
SELECT m.*, 1 - (m.embedding <=> '[1.2, 0.5, 0.8, ...]') AS similarity
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = '你的用户id'
  AND c.id = '你的会话ID'
  AND m.embedding IS NOT NULL
ORDER BY m.embedding <=> '[1.2, 0.5, 0.8, ...]'
LIMIT 5;

数据量大了之后,别忘建 HNSW 索引加速向量检索(这也是 pgvector 的核心卖点之一):

sql 复制代码
CREATE INDEX ON messages USING hnsw (embedding vector_cosine_ops);

七、Node.js + pg 实战:连接池与参数化查询

理论知识讲完,开始写代码。第一步是数据库连接层。服务器代码和数据库是相互独立 的进程,而数据库能同时服务的连接是有限的------如果每个 SQL 请求都新建连接,很快就把数据库打爆。所以要用连接池:SQL 需求多就排队等待,执行完的连接归还复用。

js 复制代码
// db.mjs ------ 全局唯一的数据库连接与 SQL 执行封装
import "dotenv/config";
import pg from "pg"; // PostgreSQL 官方 Node 驱动

const { Pool } = pg;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL, // 从 .env 读取连接串
});

// 任何 SQL 执行都走这里:text 是 SQL,params 是参数数组
async function query(text, params) {
  return pool.query(text, params);
}

export { pool, query };

注意这里的两个 PG 特色,和 MySQL 完全不同:

1. 参数占位符是 $1、$2,不是 ?

js 复制代码
// MySQL 风格:?
// INSERT INTO users (name) VALUES (?),  [name]

// PostgreSQL 风格:$1
await query("INSERT INTO users (name) VALUES ($1) RETURNING *", [name]);

2. RETURNING * 子句

插入/更新成功后,直接把这一条记录的所有字段返回,省掉一次回查:

js 复制代码
const { rows } = await query(
  "INSERT INTO users (name) VALUES ($1) RETURNING *",
  [name]
);
return rows[0]; // { id: 1, name: '张三', created_at: ... }

使用 $1 参数占位符而不是字符串拼接 SQL,是防 SQL 注入的第一道防线------永远不要把用户输入拼进 SQL 字符串。


八、完整 CRUD:users / conversations / messages

有了连接层,CRUD 就是模板化的活儿。以用户模块为例(会话、消息模块结构完全一致):

js 复制代码
// users.mjs
import { query } from "./db.mjs";

// 创建用户:RETURNING * 直接拿回整条记录
async function createUser(name) {
  const { rows } = await query(
    "INSERT INTO users (name) VALUES ($1) RETURNING *",
    [name]
  );
  return rows[0];
}

// 按 id 查询:查不到返回 null
async function getUserById(id) {
  const { rows } = await query("SELECT * FROM users WHERE id = $1", [id]);
  return rows[0] ?? null;
}

async function getAllUsers() {
  const { rows } = await query("SELECT * FROM users ORDER BY id");
  return rows;
}

async function updateUser(id, name) {
  const { rows } = await query(
    "UPDATE users SET name = $1 WHERE id = $2 RETURNING *",
    [name, id]
  );
  return rows[0] ?? null;
}

// 删除:rowCount > 0 说明真的删掉了
async function deleteUser(id) {
  const { rowCount } = await query("DELETE FROM users WHERE id = $1", [id]);
  return rowCount > 0;
}

export { createUser, getUserById, getAllUsers, updateUser, deleteUser };

会话模块同理,注意"按用户查会话列表"要按时间倒序:

js 复制代码
// conversations.mjs(节选)
async function createConversation(userId, title = null) {
  const { rows } = await query(
    "INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *",
    [userId, title]
  );
  return rows[0];
}

async function getConversationsByUserId(userId) {
  const { rows } = await query(
    "SELECT * FROM conversations WHERE user_id = $1 ORDER BY created_at DESC",
    [userId]
  );
  return rows;
}

九、语义搜索落地:Embedding + pgvector

消息模块是整个系统的灵魂:写入时可选地生成向量,查询时支持语义检索。使用 @langchain/openai 调用 OpenAI 兼容的 embedding 接口(text-embedding-v3),配合 pgvector 的 <=> 运算符:

js 复制代码
// messages.mjs
import "dotenv/config";
import { OpenAIEmbeddings } from "@langchain/openai";
import { query } from "./db.mjs";

const VALID_ROLES = ["user", "assistant", "system"];

let embeddings;

// 懒加载单例:embeddings 实例只创建一次
function getEmbeddings() {
  if (!embeddings) {
    embeddings = new OpenAIEmbeddings({
      model: process.env.EMBEDDING_MODEL || "text-embedding-v3",
      apiKey: process.env.OPENAI_API_KEY,
      configuration: {
        baseURL: process.env.OPENAI_BASE_URL, // 可指向火山方舟等兼容服务
      },
    });
  }
  return embeddings;
}

// 创建消息:withEmbedding 为 true 时同时写入向量
async function createMessage(conversationId, role, content, withEmbedding = false) {
  if (!VALID_ROLES.includes(role)) {
    throw new Error(`role 必须是 ${VALID_ROLES.join("、")} 之一`);
  }

  if (withEmbedding) {
    const vector = await getEmbeddings().embedQuery(content);

    const { rows } = await query(
      `INSERT INTO messages (conversation_id, role, content, embedding)
       VALUES ($1, $2, $3, $4::vector)
       RETURNING id, conversation_id, role, content, created_at`,
      [conversationId, role, content, JSON.stringify(vector)]
    );
    return rows[0];
  }

  const { rows } = await query(
    `INSERT INTO messages (conversation_id, role, content)
     VALUES ($1, $2, $3)
     RETURNING *`,
    [conversationId, role, content]
  );
  return rows[0];
}

// 语义检索:把查询文本转向量,按余弦距离升序取最相似的 N 条
async function searchSimilarMessages(conversationId, searchText, limit = 5) {
  const vector = await getEmbeddings().embedQuery(searchText);

  const { rows } = await query(
    `SELECT id, conversation_id, role, content, created_at,
            1 - (embedding <=> $1::vector) AS similarity
     FROM messages
     WHERE conversation_id = $2 AND embedding IS NOT NULL
     ORDER BY embedding <=> $1::vector
     LIMIT $3`,
    [JSON.stringify(vector), conversationId, limit]
  );
  return rows;
}

export {
  createMessage,
  getMessageById,
  getMessagesByConversationId,
  updateMessage,
  deleteMessage,
  searchSimilarMessages,
};

主入口串联整个流程,验证"语义检索"的效果:

js 复制代码
// index.mjs
import { pool } from "./db.mjs";
import * as users from "./users.mjs";
import * as conversations from "./conversations.mjs";
import * as messages from "./messages.mjs";

async function run() {
  // 1. 造一个用户和会话
  const user = await users.createUser("张三");
  const conversation = await conversations.createConversation(user.id, "第一次对话");

  // 2. 写入带向量的种子消息
  const seedMessages = [
    { role: "user", content: "PostgreSQL 支持哪些数据类型?" },
    { role: "assistant", content: "PostgreSQL 支持整数、文本、JSON、数组,以及 pgvector 扩展提供的向量类型。" },
    { role: "user", content: "怎么做相似度搜索?" },
    { role: "assistant", content: "可以使用 pgvector 的 cosine 距离运算符 <=>,配合 hnsw 索引加速向量检索。" },
  ];
  for (const msg of seedMessages) {
    await messages.createMessage(conversation.id, msg.role, msg.content, true);
  }

  // 3. 语义检索:"向量相似度怎么查" ------ 不包含任何关键词,但能命中第 4 条
  const query = "向量相似度怎么查";
  const results = await messages.searchSimilarMessages(conversation.id, query, 2);
  console.log(results);
}

run()
  .catch((err) => {
    console.error("运行失败", err.message);
    process.exit(1);
  })
  .finally(() => pool.end()); // 记得关池,进程才能正常退出

这就是"记忆":用户问的语句和历史上任何一条都不完全一样,但语义最接近的那条消息被捞了出来------接下来把它塞进 LLM 的上下文,Agent 就"想起来了"。


十、工程化:NestJS + TypeORM 的 MVC 分层

脚本化演示跑通了,但真实项目要工程化。这里用 NestJS (Node 生态最流行的后端框架)+ TypeORM 把上面的能力封装成 RESTful API。

10.1 一条命令生成资源模块

bash 复制代码
nest g res conversations --no-spec

这会自动创建资源型的 conversations 模块,自带 RESTful CRUD 的骨架。NestJS 的两大特色贯穿始终:

  1. MVC 模块化:Module 声明 → Controller 路由 → Service 数据操作;
  2. 依赖注入 :Service 等对象不用手动 new,框架自动注入,各层解耦。
ts 复制代码
// conversations.module.ts ------ 模块的"声明书"
import { Module } from '@nestjs/common';
import { ConversationsService } from './conversations.service';
import { ConversationsController } from './conversations.controller';

@Module({
  controllers: [ConversationsController],
  providers: [ConversationsService],
})
export class ConversationsModule {}

10.2 Controller:装饰器驱动的路由层

Controller 只负责"接请求、发响应",用装饰器声明路由,逻辑全部下沉到 Service:

ts 复制代码
// conversations.controller.ts
import {
  Controller, Get, Post, Body,
  Patch, Param, Query, Delete,
  DefaultValuePipe, ParseIntPipe,
} from '@nestjs/common';
import { SemanticSearchDto } from './dto/semantic-search.dto';
import { ConversationsService } from './conversations.service';

@Controller('conversations')
export class ConversationsController {
  constructor(private readonly conversationsService: ConversationsService) {}

  // 查询某用户的全部会话(含消息)
  @Get('/users/:userId')
  findByUser(@Param('userId', ParseIntPipe) userId: number) {
    return this.conversationsService.findConversationsByUserId(userId);
  }

  // 语义检索:POST /conversations/:id/search
  @Post(':id/search')
  search(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: SemanticSearchDto,
    @Query('limit', new DefaultValuePipe(5), ParseIntPipe) queryLimit: number,
  ) {
    const limit = dto.limit ?? queryLimit;
    return this.conversationsService.searchSimilarMessages(id, dto.query, limit);
  }

  @Get()
  findAll() {
    return this.conversationsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.conversationsService.findOne(+id); // +id:字符串转数字的小技巧
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() dto: UpdateConversationDto) {
    return this.conversationsService.update(+id, dto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.conversationsService.remove(+id);
  }
}

值得记的三个点:

  • ParseIntPipe:把路径参数/查询参数自动转成数字,用户传了非法值直接报 400,不用自己在代码里判断;
  • DefaultValuePipe(5) + ParseIntPipe 组合:limit 没传时给默认值 5,再转数字------注意管道按声明顺序执行;
  • @Get(':id') 这类动态路由要放在具体路由后面,否则可能"抢走"请求。

10.3 Service:依赖注入 + TypeORM

Service 是真正的数据操作层。NestJS 的特色是依赖注入 :通过 @InjectEntityManager() 把 TypeORM 的统一操作对象 EntityManager 注入进来,直接用面向对象的方式代替命令式 SQL:

ts 复制代码
// conversations.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class ConversationsService {
  constructor(
    @InjectEntityManager()
    private readonly em: EntityManager,
  ) {}

  // 用面向对象代替命令式 SQL:查用户,连带着把会话一对多关系加载出来
  async findConversationsByUserId(userId: number) {
    const user = await this.em.findOne(User, {
      where: { id: userId },
      relations: { conversations: true },        // 预加载关联会话
      order: { conversations: { createdAt: 'DESC' } },
    });
    if (!user) {
      throw new NotFoundException(`User #${userId} not found`);
    }
    return user;
  }

  // 语义检索:TypeORM 不原生支持 pgvector 的 <=>,用原生 SQL 最直接
  async searchSimilarMessages(conversationId: number, query: string, limit: number) {
    const vector = await getEmbedding(query); // 复用第九节的 embedding 逻辑

    return this.em.query(
      `SELECT id, role, content, created_at,
              1 - (embedding <=> $1::vector) AS similarity
       FROM messages
       WHERE conversation_id = $2 AND embedding IS NOT NULL
       ORDER BY embedding <=> $1::vector
       LIMIT $3`,
      [JSON.stringify(vector), conversationId, limit],
    );
  }
}

实体类是 TypeORM 的"八股文"------把表结构映射成类,ORM 全靠它工作:

ts 复制代码
// entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Conversation } from './conversation.entity';

@Entity('users') // 对应 users 表
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  // 一对多:一个用户有多条会话
  @OneToMany(() => Conversation, (conversation) => conversation.user)
  conversations: Conversation[];
}
ts 复制代码
// entities/conversation.entity.ts(节选)
@Entity('conversations')
export class Conversation {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToOne(() => User, (user) => user.conversations)
  user: User;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;
}

10.4 DTO:约束提交规则,不合法直接报错退出

DTO(Data Transfer Object,数据传输对象)负责约束前端提交的规则------无论表单、params 还是 queryString,先过校验,不合法就直接报错退出,绝不进入业务逻辑:

ts 复制代码
// dto/semantic-search.dto.ts
import { IsString, IsInt, IsOptional, Min, Max } from 'class-validator';

export class SemanticSearchDto {
  @IsString()
  query: string;   // 必填:要检索的文本

  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(20)
  limit?: number;  // 可选:返回条数,默认由管道兜底为 5
}

别忘在入口全局启用 ValidationPipe,否则 DTO 装饰器不会生效:

ts 复制代码
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

至此,一条完整的链路打通了:前端 POST 一个搜索词 → Controller 接参并校验 → Service 注入 EntityManager → 文本转向量 → pgvector <=> 余弦距离检索 → 最相似的历史消息返回给 LLM 作为记忆上下文。


十一、知识点速查表(复习专用)

主题 核心要点
表设计 users 1---N conversations 1---N messages,层层一对多
参数化查询 PostgreSQL 用 $1 占位符(MySQL 是 ?),防 SQL 注入
RETURNING * 插入/更新后直接返回记录字段,省一次回查
连接池 数据库连接有限,SQL 多就排队,用后归还复用
pgvector CREATE EXTENSION vector,加一个 vector(维度) 字段即可
余弦距离 1 - 余弦相似度,越小越相似
<=> 运算符 pgvector 余弦距离运算符,ORDER BY embedding <=> 查询向量 LIMIT n
向量写入 $4::vector 强制转类型,JSON.stringify(vector) 传数组
JOIN INNER 交集 / LEFT 左为主 / RIGHT 右为主 / FULL 并集
NestJS Module 声明、Controller 路由(装饰器)、Service 数据操作
依赖注入 @InjectEntityManager() 注入 TypeORM 的 EntityManager
Entity 实体类 = 表的映射,ORM 工作基础
DTO 约束前端提交规则,不合法直接报错退出

总结

回到开头的公式:MySQL + Milvus = PostgreSQL。

在 AI Agent 的场景里,PostgreSQL + pgvector 用一个扩展、一个向量字段,同时承接了关系查询和语义检索两类诉求------不用拆架构、不用双写同步、不用维护两套系统。Node.js 侧用 pg 驱动的连接池和参数化查询保证稳定与安全;工程侧用 NestJS 的 MVC 分层(Module / Controller / Service)+ TypeORM 的实体映射与 DTO 校验,把"长时记忆"封装成标准的 RESTful API。

这套思路不只能存聊天记录------知识库问答、RAG 检索、日志语义分析,都是同一套模式。从一张消息表出发,你已经掌握了 AI 应用后端最核心的一块拼图。


参考文档

相关推荐
用户713874229001 小时前
在 .NET 中使用 AWS Secrets Manager:Config 与 Client 详解
后端
福大大架构师每日一题1 小时前
2026-10-16:交替数列的最大元素。用go语言,给定三个整数 n、s、m。现在要构造一个包含 n 个整数的序列,其中第一个数必须等于 s。序列中相邻数字的
后端
yunwei371 小时前
eBPF 入门实践教程:BPF Token,安全的委托式权限与程序加载
linux·后端·性能优化
136096757231 小时前
旧 SQL 在新 PG 上能不能跑
后端
SamDeepThinking1 小时前
配货单发货数为空告警误报治理:写表延迟复核方案
java·后端·架构
知守观1 小时前
mirrorOf= 把私服请求拦去了公网镜像:本地能跑,CI 上内部包 404 排查
后端·ci/cd·maven
ly76891 小时前
读懂 PostgreSQL 执行计划:从 EXPLAIN 输出定位 Spring Boot 慢 SQL 的代价估算偏差
spring boot·sql·postgresql·执行计划·explain analyze·慢sql优化
万物智能1 小时前
鸿蒙UART总线怎么用?—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
后端
UrbanJazzerati1 小时前
AutoHotkey v2 零基础快速入门 Part 5 游戏场景明暗阈值采集工具
后端