仅在模式搭建步骤中使用 SYS。sqlplus 命令中的密码为 docker run 命令的 ORACLE_PWD 值;CREATE USER 中的密码是应用密码,我们将导出为 DB_PASSWORD。
在数据库容器内运行设置。
# Create the application user and grant the privileges needed for the demo.dockerexec-i oracle-free-26ai-vector \
sqlplus -s'sys/"replace-with-oracle-pwd"@FREEPDB1 as sysdba'<<'SQL'
WHENEVER SQLERROR EXIT SQL.SQLCODE
CREATE USER vector_demo IDENTIFIED BY "replace-with-vector-demo-password";
GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO vector_demo;
EXIT
SQL技术支持
import os
import oracledb
ADMIN_USERS ={"SYS","SYSTEM","PDBADMIN"}defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defmain():# Load required database settings from environment variables.
user = required_env("DB_USER")
password = required_env("DB_PASSWORD")
dsn = required_env("DB_DSN")if user.upper()in ADMIN_USERS:raise SystemExit("Use the vector_demo application user, not an admin user.")# Open a Thin-mode connection with the application user.with oracledb.connect(user=user, password=password, dsn=dsn)as connection:
mode ="thin"if oracledb.is_thin_mode()else"thick"print("Connected to Oracle Database.")print(f"Driver mode: {mode}")print(f"Database version: {connection.version}")if __name__ =="__main__":
main()技术支持
import array
import os
import oracledb
ADMIN_USERS ={"SYS","SYSTEM","PDBADMIN"}defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defmain():
user = required_env("DB_USER")if user.upper()in ADMIN_USERS:raise SystemExit("Use the vector_demo application user, not an admin user.")# Define three small vectors so the distance calculation is easy to inspect.
rows =[("apple", array.array("f",[1.0,0.0,0.0])),("banana", array.array("f",[0.9,0.1,0.0])),("car", array.array("f",[0.0,1.0,0.0])),]with oracledb.connect(
user=user,
password=required_env("DB_PASSWORD"),
dsn=required_env("DB_DSN"),)as connection:with connection.cursor()as cursor:# Recreate the table so the script can be run repeatedly.
cursor.execute("""
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE manual_vectors PURGE';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE <> -942 THEN
RAISE;
END IF;
END;
""")
cursor.execute("""
CREATE TABLE manual_vectors (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
label VARCHAR2(100),
embedding VECTOR(3, FLOAT32)
)
""")# Bind each vector as a 32-bit floating-point array.
cursor.executemany("""
INSERT INTO manual_vectors (label, embedding)
VALUES (:1, :2)
""",
rows,)
connection.commit()# Rank rows by Euclidean distance from the query vector.
cursor.execute("""
SELECT label,
VECTOR_DISTANCE(
embedding,
:query_vector,
EUCLIDEAN
) AS distance
FROM manual_vectors
ORDER BY distance
FETCH EXACT FIRST 2 ROWS ONLY
""",
query_vector=array.array("f",[1.0,0.0,0.0]),)print("Created manual_vectors.")print("Inserted 3 manual vectors.")print("\nNearest manual vectors:")for rank,(label, distance)inenumerate(cursor, start=1):print(f"{rank}. {label:<6} distance={float(distance):.4f}")if __name__ =="__main__":
main()技术支持
import os
import oracledb
ADMIN_USERS ={"SYS","SYSTEM","PDBADMIN"}defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defconfigured_dimension():try:
dimension =int(required_env("EMBEDDING_DIM"))except ValueError as exc:raise SystemExit("EMBEDDING_DIM must be an integer.")from exc
if dimension <=0:raise SystemExit("EMBEDDING_DIM must be a positive integer.")return dimension
defmain():
user = required_env("DB_USER")if user.upper()in ADMIN_USERS:raise SystemExit("Use the vector_demo application user, not an admin user.")# Validate the configured model name and vector dimension.
model_name = required_env("EMBEDDING_MODEL")
dimension = configured_dimension()with oracledb.connect(
user=user,
password=required_env("DB_PASSWORD"),
dsn=required_env("DB_DSN"),)as connection:with connection.cursor()as cursor:# Recreate the documents table so the script is repeatable.
cursor.execute("""
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE documents PURGE';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE <> -942 THEN
RAISE;
END IF;
END;
""")
cursor.execute(f"""
CREATE TABLE documents (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR2(200) NOT NULL,
content VARCHAR2(1000) NOT NULL,
embedding VECTOR({dimension}, FLOAT32)
)
""")print(f"Embedding model: {model_name}")print(f"Embedding dimension: {dimension}")print("Recreated documents table.")if __name__ =="__main__":
main()技术支持
import os
from openai import OpenAI
defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defconfigured_dimension():try:returnint(required_env("EMBEDDING_DIM"))except ValueError as exc:raise SystemExit("EMBEDDING_DIM must be an integer.")from exc
defmain():
required_env("OPENAI_API_KEY")
model_name = required_env("EMBEDDING_MODEL")
expected_dimension = configured_dimension()# Request one embedding to confirm the model output dimension.
client = OpenAI()
response = client.embeddings.create(
model=model_name,input="Oracle AI Vector Search dimension check",)
returned_dimension =len(response.data[0].embedding)print(f"Embedding model: {model_name}")print(f"Returned embedding length: {returned_dimension}")print(f"Configured EMBEDDING_DIM: {expected_dimension}")# Stop before loading data if the table dimension is incompatible.if returned_dimension != expected_dimension:raise SystemExit("Dimension check failed. Update EMBEDDING_DIM, rerun ""03_create_documents_table.py, and try again.")print("Dimension check passed.")if __name__ =="__main__":
main()技术支持
import array
import os
import oracledb
from openai import OpenAI
DOCUMENTS =[("Oracle AI Vector Search","Oracle Database can store vector embeddings in native VECTOR ""columns and query them with SQL similarity search.",),("Semantic Search for Applications","Semantic search ranks results by meaning, helping applications ""find related content even when exact words differ.",),("Vector Index Workflow","Vector indexes help approximate nearest-neighbor search scale ""when datasets grow beyond small examples.",),("Local Docker Development","Docker containers make it practical to run a local database for ""development and repeatable tutorials.",),("Python Database Code","Python applications can connect to Oracle Database with the ""oracledb driver and use environment variables for credentials.",),("Acoustic Guitar Practice","Daily guitar practice improves timing, chord transitions, and ""confidence when learning new songs.",),("Weeknight Vegetable Soup","A simple soup can combine onions, carrots, beans, herbs, and ""broth for an easy weeknight meal.",),]
ADMIN_USERS ={"SYS","SYSTEM","PDBADMIN"}defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defconfigured_dimension():try:returnint(required_env("EMBEDDING_DIM"))except ValueError as exc:raise SystemExit("EMBEDDING_DIM must be an integer.")from exc
defmain():
user = required_env("DB_USER")if user.upper()in ADMIN_USERS:raise SystemExit("Use the vector_demo application user, not an admin user.")
required_env("OPENAI_API_KEY")
model_name = required_env("EMBEDDING_MODEL")
expected_dimension = configured_dimension()# Generate embeddings for all sample documents in one API request.
client = OpenAI()
response = client.embeddings.create(
model=model_name,input=[content for _, content in DOCUMENTS],)# Restore the response order using each item's index.
embeddings =[
item.embedding
for item insorted(
response.data,
key=lambda item: item.index,)]ifany(len(embedding)!= expected_dimension for embedding in embeddings):raise SystemExit("An embedding length did not match EMBEDDING_DIM. ""Rerun 04_check_embedding_dimension.py.")# Bind each embedding as a 32-bit floating-point array.
rows =[(title, content, array.array("f", embedding))for(title, content), embedding inzip(DOCUMENTS, embeddings)]# Replace the sample rows and commit the transaction.with oracledb.connect(
user=user,
password=required_env("DB_PASSWORD"),
dsn=required_env("DB_DSN"),)as connection:with connection.cursor()as cursor:
cursor.execute("DELETE FROM documents")
cursor.executemany("""
INSERT INTO documents (title, content, embedding)
VALUES (:1, :2, :3)
""",
rows,)
cursor.execute("SELECT COUNT(*) FROM documents")
row_count = cursor.fetchone()[0]
connection.commit()print(f"Embedding model: {model_name}")print(f"Generated embeddings for {len(DOCUMENTS)} documents.")print(f"Inserted {len(DOCUMENTS)} documents.")print(f"documents row count: {row_count}")if __name__ =="__main__":
main()技术支持
当嵌入模型发生变化时,精确排名与距离可能不同。然而,与 Oracle AI 向量搜索、语义搜索、Python 数据库代码、向量索引相关的 documents 应该排在前列。
Query: Where should an AI app keep meaning-based search data?
Top 5 results:
1. Oracle AI Vector Search
distance: <cosine distance from your run>
Oracle Database can store vector embeddings in native VECTOR columns and query them with SQL...
2. Semantic Search for Applications
distance: <cosine distance from your run>
Semantic search ranks results by meaning, helping applications find related content even when...
3. Python Database Code
distance: <cosine distance from your run>
Python applications can connect to Oracle Database with the oracledb driver and use environment...
4. Vector Index Workflow
distance: <cosine distance from your run>
Vector indexes help approximate nearest-neighbor search scale when datasets grow beyond small...
5. Local Docker Development
distance: <cosine distance from your run>
Docker containers make it practical to run a local database for development and repeatable...技术支持
Semantic query: Where should an AI app keep meaning-based search data?
Keyword phrase: meaning-based search data
Semantic results:
1. Oracle AI Vector Search
distance: <cosine distance from your run>
2. Semantic Search for Applications
distance: <cosine distance from your run>
3. Python Database Code
distance: <cosine distance from your run>
Keyword results:
0 exact phrase matches技术支持
本教程不使用 EXPLAIN PLAN 或 DBMS_XPLAN,因为我们通过模式元数据验证索引创建,而非通过执行计划。
创建脚本
将以下代码保存为 08_create_vector_index.py。
import array
import os
import oracledb
from openai import OpenAI
INDEX_NAME ="DOCUMENT_EMBEDDING_IDX"
QUERY_TEXT ="Where should an AI app keep meaning-based search data?"
ADMIN_USERS ={"SYS","SYSTEM","PDBADMIN"}defrequired_env(name):
value = os.getenv(name)ifnot value:raise SystemExit(f"Set {name} before running this script.")return value
defmain():
user = required_env("DB_USER")if user.upper()in ADMIN_USERS:raise SystemExit("Use the vector_demo application user, not an admin user.")
required_env("OPENAI_API_KEY")# Generate the query vector used for the validation search.
client = OpenAI()
response = client.embeddings.create(
model=required_env("EMBEDDING_MODEL"),input=QUERY_TEXT,)
query_vector = array.array("f", response.data[0].embedding)with oracledb.connect(
user=user,
password=required_env("DB_PASSWORD"),
dsn=required_env("DB_DSN"),)as connection:with connection.cursor()as cursor:# Remove a prior index so the script can be rerun safely.try:
cursor.execute(f"DROP INDEX {INDEX_NAME}")except oracledb.DatabaseError as exc:
error = exc.args[0]if error.code !=1418:raise# Create an approximate neighbor-partitions vector index.
cursor.execute("""
CREATE VECTOR INDEX document_embedding_idx
ON documents (embedding)
ORGANIZATION NEIGHBOR PARTITIONS
DISTANCE COSINE
WITH TARGET ACCURACY 95
""")# Confirm that Oracle reports the vector index as valid.
cursor.execute("""
SELECT index_name, index_type, status
FROM user_indexes
WHERE index_name = :index_name
""",
index_name=INDEX_NAME,)
index_row = cursor.fetchone()# Confirm that semantic search still returns rows after index creation.
cursor.execute("""
SELECT title
FROM documents
ORDER BY VECTOR_DISTANCE(embedding, :query_vector, COSINE)
FETCH EXACT FIRST 3 ROWS ONLY
""",
query_vector=query_vector,)
rows = cursor.fetchall()ifnot index_row:raise SystemExit("Vector index was not found in USER_INDEXES.")print("Created vector index DOCUMENT_EMBEDDING_IDX.")print("\nIndex validation:")print(f"Index name: {index_row[0]}")print(f"Index type: {index_row[1]}")print(f"Status: {index_row[2]}")
result_count =len(rows)print("\nSemantic search still returns "f"{result_count} results after index creation.")if __name__ =="__main__":
main()技术支持
Created vector index DOCUMENT_EMBEDDING_IDX.
Index validation:
Index name: DOCUMENT_EMBEDDING_IDX
Index type: VECTOR
Status: VALID
Semantic search still returns 3 results after index creation.技术支持
Mark Nelson 是 Oracle 的架构师兼开发者布道师。他专注于人工智能、微服务与数据库技术的融合。他是活跃的博主、出版作者、Manning Publications 的技术审稿人、Stanford Code in Place 的章节负责人,以及 DeepLearning.ai 的导师。他经常在 Java 与 Oracle 用户组、AI 线下活动以及各类大型会议上演讲。他热爱学习与分享,拥有在 IBM 和 Oracle 超过三十年的行业经验。
在本教程中,我们将使用 Oracle AI 向量搜索、Python 和 Oracle Database Free 26ai 构建一个本地语义搜索演示。
我们先从手写的三维向量开始,便于直观看到距离计算;然后生成文本嵌入向量,并通过 SQL 从 Oracle 数据库中查询它们。
本教程面向对向量搜索陌生、但熟悉 Docker、环境变量、Python 包以及本地数据库搭建的 Python 与 SQL 开发者。
AI 驱动的应用需要能理解“含义”的搜索,而不仅是精确的词面匹配。无论是支持应用、文档门户,还是内部知识工具,都应能在最佳匹配文档写着“将向量嵌入存入原生列”的情况下,找到“AI 搜索的数据库存储”。
向量嵌入通过将文本表示为数值向量,使这一点成为可能。Oracle AI 向量搜索让我们可以将这些向量直接存入 Oracle 数据库,用 SQL 查询它们,并将嵌入与关系型应用数据并存。
在这个本地流程中,我们不需要单独的向量数据库。
我们将在本地运行 Oracle Database Free 26ai,从 Python 通过
oracledbThin 模式连接,先存储手工向量,再存储 OpenAI 嵌入向量,运行语义搜索,用简单的短语精确匹配对比语义检索,并创建一个向量索引,通过USER_INDEXES进行验证。最终,我们将获得一个可运行的、由 Python 驱动的语义搜索流程:在 Oracle 数据库中存储嵌入向量,并在本地检索最相似的
documents。什么是 Oracle AI 向量搜索?
Oracle AI 向量搜索是一组 Oracle 数据库能力,用于存储、索引和查询向量嵌入。向量嵌入是一个固定长度的数值列表,以数据库可进行数学比较的形式,表示文本、图像或其他数据的含义。
嵌入模型负责生成数值向量。它接收输入(如句子、段落、图像描述或代码片段),并返回一个维度固定的向量。
相似的输入应产生彼此接近的向量,而不相关的输入应相距更远。因此,我们必须对存储的文档与传入查询使用同一个嵌入模型,因为只有当向量共享同一坐标系时,距离才有意义。
在本教程中,手工向量刻意设置得很小,便于检查数学计算。真实的文本嵌入要大得多,因为模型需要足够的维度来编码词语、短语与主题之间更细微的关系。
向量搜索按向量之间的距离对行进行排序。对于欧氏距离和余弦距离查询,距离值越小表示匹配越近。在本教程中,我们将先使用一个小的
VECTOR(3, FLOAT32)列,直接观察距离行为,然后转向从文本生成的VECTOR(1536, FLOAT32)嵌入。图 1. Oracle AI 向量搜索流程
Oracle AI 向量搜索将嵌入存储在原生的VECTOR 列中,并按向量距离对结果进行排序。
当嵌入需要与关系型应用数据(如文档 ID、标题、内容、用户、产品、工单或元数据)并存时,Oracle 数据库非常有用。
一些应用会为特殊检索负载使用单独的向量数据库或搜索引擎,但对于本教程的本地语义搜索流程,Oracle 数据库就能提供我们所需的向量存储与搜索功能。
Oracle AI 向量搜索的关键特性
在演示中我们将用到四项功能:原生向量存储、距离函数、向量索引,以及与关系查询的集成。
原生向量数据类型
Oracle 数据库支持原生
VECTOR列,并能明确维度与元素格式。在演示中,我们将对手工向量使用VECTOR(3, FLOAT32),对默认的text-embedding-3-small嵌入模型使用VECTOR(1536, FLOAT32)。距离函数与运算符
可运行示例使用
VECTOR_DISTANCE(),因为它能在 SQL 中显式保留距离度量方式。我们将在手工向量示例中使用欧氏距离,在文本嵌入中使用余弦距离。
Oracle 数据库也包含向量距离的简写运算符,以及与点积相关的距离行为,但本教程将保持在显式的
VECTOR_DISTANCE()路径上,便于直接运行 SQL。不同的距离度量在“接近度”的含义上回答的问题略有不同。
在小型示例场景下,余弦距离是文本嵌入的良好默认选择,因为它关注方向,通常更契合语义相似性。
欧氏距离更易于用手工向量进行可视化;当向量幅度或归一化是模型设计的一部分时,点积式比较在一些检索系统中也很有用。
度量
比较的内容
适用场景
注意事项
余弦
向量方向
文本嵌入与语义相似性
结果依赖于使用同一嵌入模型与一致的预处理
欧氏
直线距离
小型示例、几何直觉、部分数值特征向量
幅度会影响距离,因而缩放可能很重要
点积
方向与幅度共同作用
围绕归一化向量或内积打分设计的工作流
在比较结果前需要理解分值方向与归一化规则
向量索引
精确向量搜索对正确性与小数据集很有用。随着数据集增长,向量索引可支持近似最近邻搜索工作流。
Oracle AI 向量搜索包含内存邻接图索引(常与 HNSW 风格搜索相关)与邻接分区索引(常与 IVF 风格分区相关)。
在本地演示中,我们使用
ORGANIZATION NEIGHBOR PARTITIONS,并通过USER_INDEXES验证索引。近似索引以牺牲少量穷举搜索的确定性,换取规模化下更快的检索。
合适的索引家族取决于工作负载、数据规模、更新模式与召回目标。
本教程选择 IVF 风格的邻接分区索引,因为它使本地搭建更简单,并提供具体的索引创建步骤,而不会把教程变成索引调优指南。
索引家族
基本思想
优势
取舍
可能的使用场景
IVF / 邻接分区
对向量空间进行分区,再搜索可能的分区
心智模型简单,适用于许多批量加载的数据集
召回与速度取决于分区与搜索设置
我们希望为不断增长的数据集提供直接的近似搜索路径
HNSW / 邻接图
构建连接近邻向量的图,以便快速遍历
对许多最近邻工作负载有较强的召回与延迟表现
更偏内存,且配置影响更大
我们需要低延迟搜索,并能承担内存与调优投入
与关系查询集成
向量列可以与普通的关系列并存。我们的documents 表会同时存储标题、内容与嵌入向量,因此 SQL 可以同时返回相似度分数与原始文本文档。
如何开始使用 Oracle AI 向量搜索
我们将采用一种本地搭建路径:使用 Docker 中的 Oracle Database Free 26ai、
FREEPDB1服务,以及一个专用的vector_demo应用用户。向量搜索概念本身较易上手,但搭建过程属于中级难度,因为我们要用到 Docker、数据库模式、Python 包、环境变量以及嵌入 API 密钥。
先决条件
我们需要:
本演示需要包含此处所用向量功能的 Oracle 数据库版本或环境,包括原生
VECTOR列、VECTOR_DISTANCE()与CREATE VECTOR INDEX。本地路径使用下方镜像标签的 Oracle Database Free 26ai,且 DSN 使用
FREEPDB1可插拔数据库服务。手工向量步骤不需要
OpenAIAPI 密钥。语义步骤会将示例文档文本与查询文本发送给配置的嵌入提供商,因此我们只使用示例或非敏感文本。本教程使用
oracledb,即当前的 Oracle 数据库 Python 驱动。python-oracledb默认以 Thin 模式运行,因此本地演示无需 Oracle Client 库。Thick 模式在其他部署中很有用,但此处不会调用oracledb.init_oracle_client()。在本地启动 Oracle Database Free 26ai
为我们将要编写的脚本创建一个项目目录。
使用本地容器镜像启动 Oracle Database Free 26ai。
检查容器日志,并等待数据库报告启动完成。
FREEPDB1是本地 Oracle Database Free 容器路径所用的可插拔数据库服务。我们的 Python DSN 将是localhost:1521/FREEPDB1。在 FREEPDB1 中创建 vector_demo 模式
仅在模式搭建步骤中使用
SYS。sqlplus 命令中的密码为 docker run 命令的 ORACLE_PWD 值;CREATE USER 中的密码是应用密码,我们将导出为DB_PASSWORD。在数据库容器内运行设置。
这是演示容器的本地开发捷径。由于表使用了标识列,因此需要 CREATE SEQUENCE。
在生产环境中,我们会与数据库管理员一起审查权限,并根据具体的应用工作负载应用最小权限原则。
安装 Python 依赖
创建并激活虚拟环境。
安装演示所用的 Python 包。
设置环境变量
将数据库凭据和嵌入配置存入环境变量,而不是在 Python 文件中硬编码。
OPENAI_API_KEY仅在嵌入与语义搜索步骤中需要。EMBEDDING_DIM必须与所选嵌入模型返回的维度数量一致。Oracle AI 向量搜索演示项目
我们将通过八个检查点构建演示。每个脚本引入一个概念,并产生可在继续前验证的输出。
仅数据库部分在步骤 1–3 完成。届时,我们已连接到 Oracle 数据库、存储手工向量、按距离查询它们,并创建语义表。
OpenAIAPI 密钥从第 4 步开始需要。步骤 1:连接到本地数据库
首先,我们要验证 Python 能以
vector_demo应用用户身份连接到 Oracle 数据库。该脚本从环境中读取DB_USER, DB_PASSWORD与DB_DSN,并打印驱动模式与数据库版本。创建脚本
将以下代码保存为
01_check_connection.py。运行脚本
运行连接检查。
预期输出
输出应确认连接成功,且驱动处于 Thin 模式。
在创建任何向量表之前,我们已确认本地数据库、服务名、凭据与 Python 驱动均正常工作。
步骤 2:用手工向量建立直觉
在引入嵌入模型之前,先存储三个便于推理的小向量。
array.array("f", values)会创建一个 32 位浮点数组,python-oracledb可将其绑定到VECTOR(..., FLOAT32)列中。图 2. 手工向量与欧氏距离。
手工示例使用欧氏距离。apple 向量与查询向量相同,距离为 0.0000。banana 向量第二名,因为 sqrt((1.0 - 0.9)^2 + (0.0 - 0.1)^2 + (0.0 - 0.0)^2) = 0.1414。
创建脚本
将以下代码保存为
02_manual_vectors.py。运行脚本
运行手工向量脚本。
预期输出
与查询向量相同的向量会以距离 0.0000 排在首位,邻近的 banana 向量第二位。
这就是向量搜索的核心思想:行按与查询向量的数学接近程度排序。嵌入模型会生成更大的向量,但数据库行为一致。
步骤 3:创建语义文档表
既然我们知道向量可用,就创建一个真实的应用风格表。关键在于向量列的维度必须与嵌入模型的输出维度一致。
维度是表契约的一部分。如果一个嵌入模型返回 1,536 个数值,列就必须声明为
VECTOR(1536, FLOAT32)。如果之后切换到输出长度不同的模型,我们需要重建或迁移该向量列,才能让插入与查询继续使用兼容的向量。
由于 DDL 不能将向量维度作为常规 SQL 绑定变量,脚本会将
EMBEDDING_DIM转换为整数后,再用于 CREATE TABLE 语句。创建脚本
将以下代码保存为
03_create_documents_table.py。运行脚本
运行建表脚本。
预期输出
输出应显示配置的嵌入模型与向量维度。
我们已完成仅数据库部分。Oracle 数据库可以在本地存储并比较向量;接下来,我们将用从文本生成的嵌入取代手写向量。
步骤 4:验证嵌入模型维度
在插入嵌入前,我们先向嵌入模型请求一个向量,并验证其长度。这能避免最常见的向量表错误:插入的向量长度与
VECTOR列维度不匹配。嵌入模型不仅是“文本到数字”的转换器;它定义了应用的语义空间。本演示中的所有文档与查询嵌入都必须来自同一模型。
使用单一模型可确保小距离代表相近含义,而不是两个无关向量恰巧形状相似。模型也决定向量长度,这就是我们在加载数据前要检查返回维度的原因。
创建脚本
将以下代码保存为
04_check_embedding_dimension.py。运行脚本
运行维度检查。
预期输出
对于默认模型与维度,输出应类似如下。
若之后选择不同的嵌入模型,应更新
EMBEDDING_MODEL,将EMBEDDING_DIM设为返回向量长度,并重建documents表。步骤 5:生成并插入文档嵌入
现在我们为一个小型自包含数据集生成嵌入,并将其插入 Oracle 数据库。
每个嵌入在绑定前会转换为
array.array("f", embedding),以匹配FLOAT32向量列。创建脚本
将以下代码保存为
05_insert_embeddings.py。运行脚本
运行插入脚本。
预期输出
行数应与内联的七个
documents相匹配。现在我们已在 Oracle 数据库中将文本与嵌入向量存储在一起。包含与主题无关的吉他与汤的
documents也被加入,以便搜索结果的近与远匹配更为明显。步骤 6:运行语义搜索查询
接下来,我们将对自然语言查询进行嵌入,并与存储的文档嵌入进行比较。该脚本使用余弦距离,它常用于文本嵌入,因为它关注向量方向而非原始幅度。
对于语义搜索,查询需要经过与文档 相同的嵌入模型。我们并不是让 SQL 直接理解英文句子。
我们是让嵌入模型将句子转换为向量,再让 Oracle 数据库按与该查询向量的距离对存储向量进行排序。
创建脚本
将以下代码保存为
06_semantic_search.py。运行脚本
运行语义搜索脚本。
预期输出
当嵌入模型发生变化时,精确排名与距离可能不同。然而,与 Oracle AI 向量搜索、语义搜索、Python 数据库代码、向量索引相关的
documents应该排在前列。在本教程中的欧氏与余弦距离查询里,数值越小表示匹配越近。
点积是许多向量搜索工作流中另一种有用的向量比较方法,尤其在幅度或归一化向量重要时,但分值方向与归一化需要谨慎处理。
我们将继续在可运行代码中使用欧氏与余弦距离,以保持结果解释的简明。
若在未来的应用中自行对嵌入做归一化,需要对存储的文档嵌入与查询嵌入一并、且一致地归一化;否则距离与排名会发生变化。
步骤 7:将语义搜索与短语匹配对比
语义搜索的价值在于它不要求文档中出现完全相同的短语。
让我们将语义查询与一个简单的精确短语谓词进行对比。
这不是一个全文搜索引擎演示;它是一个小型 SQL 对比,旨在直观展示字面匹配与基于含义的排序之间的差异。
创建脚本
将以下代码保存为
07_semantic_vs_keyword.py。运行脚本
运行对比脚本。
预期输出
即便没有出现精确关键短语,语义搜索也应返回相关的
documents。传统 SQL 谓词在过滤、连接、权限与精确匹配方面仍然重要。当应用需要语义检索时,向量搜索提供可与关系数据结合的基于含义的排序信号。
步骤 8:添加并验证向量索引
最后,我们将创建一个向量索引,并验证 Oracle 数据库报告其为由演示模式拥有的有效索引。
此步骤展示索引创建流程;我们的小数据集不足以得出有意义的性能结论。
没有索引时,Oracle 数据库可以将查询向量与每个存储向量逐一精确比较。这种方式适用于很小的数据集,并有助于学习。
在更大的数据集上,近似向量索引会缩小搜索空间,让查询能够快速返回,同时仍能找到近邻。
在选择索引家族前,应综合考虑数据规模、延迟目标、召回要求、内存预算,以及向量变更频率。
最后的查询会确认在创建索引后,语义搜索仍能返回结果。
本教程不使用 EXPLAIN PLAN 或 DBMS_XPLAN,因为我们通过模式元数据验证索引创建,而非通过执行计划。
创建脚本
将以下代码保存为
08_create_vector_index.py。运行脚本
运行索引脚本。
预期输出
输出应包含
DOCUMENT_EMBEDDING_IDX的一行,INDEX_TYPE为VECTOR,STATUS为VALID。USER_INDEXES确认索引存在且有效。这并不能证明某个具体查询使用了该索引,而本教程也不使用执行计划输出来作为验证方法。在生产工作负载中,我们会用真实的数据规模、查询模式与性能目标进行测试。
Oracle AI 向量搜索技巧与故障排查
数据库容器仍在启动中
等待启动完成后,再次运行
docker logs oracle-free-26ai-vector。数据库必须就绪,Python 才能连接到localhost:1521/FREEPDB1。端口 1521 已被占用
停止冲突的本地服务,或更改 Docker 端口映射。若主机端口变更,请更新
DB_DSN。数据库连接失败
核实
DB_USER, DB_PASSWORD与DB_DSN。在 DSN 中使用FREEPDB1,并以vector_demo连接应用脚本,而不是SYS, SYSTEM或PDBADMIN。缺少嵌入 API 密钥
步骤 1–3 仅需数据库。步骤 4–8 需要
OPENAI_API_KEY,且可能产生 API 成本。向量维度不匹配
运行
04_check_embedding_dimension.py,更新EMBEDDING_DIM,重新运行03_create_documents_table.py,并用05_insert_embeddings.py重新加载嵌入。向量绑定失败
确保每个
FLOAT32向量绑定都使用array.array("f", values)。教程中对手工向量、存储的文档嵌入与查询嵌入均采用此格式。语义排序与示例不同
这属正常。嵌入提供商可能更新模型,且浮点距离在不同运行间会有差异。
向量索引创建失败
确认
documents表存在、vector_demo模式拥有它,且没有残留的DOCUMENT_EMBEDDING_IDX来自未完全执行的运行。走向生产
使用
oracledb.create_pool()进行连接池化,基准测试真实数据规模,审查安全与机密管理,并基于真实查询模式调优索引。停止并移除容器
完成本教程后,停止并移除本地容器。
结语
我们用 Oracle AI 向量搜索、Oracle Database Free 26ai 与 Python 构建了一个本地语义搜索流程。
我们从手写的
VECTOR(3, FLOAT32)值开始,使用array.array("f", values)插入模型生成的嵌入,使用VECTOR_DISTANCE()查询相似的documents,以简单的短语精确谓词对比语义检索,并通过USER_INDEXES验证了一个向量索引。当嵌入需要与关系数据并存、且我们希望无需为本地流程新增独立向量数据库即可实现 SQL 可见的存储与搜索时,这种方式非常契合。
该演示故意保持小而精悍:它讲解的是基础原语,而非生产级规模。更大的应用应在真实数据下做基准测试,审查安全与权限,使用连接池,并在真实查询模式下验证索引选择。
接下来,可通过以下资源深化实现: