RAG no MariaDB na prática: monte um recomendador de produtos em 30 minutos
Passo a passo reproduzível: tabela com VECTOR, índice HNSW, carga de embeddings, busca por similaridade e filtros. Por que a busca é aproximada e por que entende qualquer idioma.

Chega de teoria (se pulou, ela está em MariaDB e IA: tokens, embeddings e vetores). Hoje é teclado. Vamos montar a parte de recuperação de um RAG de recomendação de produtos: o cliente escreve o que quer, do jeito dele, e o MariaDB devolve os produtos mais parecidos. É exatamente esse resultado que você entrega ao LLM depois.
O que você precisa
- MariaDB 11.8 ou mais recente (vetores chegaram na 11.7; a 11.8 é a primeira LTS com eles).
- Python 3 com
mariadbesentence-transformers. - Um modelo de embedding multilíngue. Uso aqui o
paraphrase-multilingual-MiniLM-L12-v2, que gera 384 dimensões e roda na CPU.
docker run -d --name mdb -e MARIADB_ROOT_PASSWORD=senha -p 3306:3306 mariadb:11.8
pip install mariadb sentence-transformers
Passo 1: a tabela
CREATE DATABASE loja;
USE loja;
CREATE TABLE produtos (
id INT PRIMARY KEY AUTO_INCREMENT,
nome VARCHAR(200) NOT NULL,
descricao TEXT NOT NULL,
categoria VARCHAR(60),
preco DECIMAL(10,2),
embedding VECTOR(384) NOT NULL,
VECTOR INDEX idx_emb (embedding) M=16 DISTANCE=cosine
);
Três detalhes que importam:
VECTOR(384)precisa ter exatamente a dimensão do seu modelo. Trocou de modelo, recalcula tudo.- A coluna indexada precisa ser
NOT NULL, e só cabe um índice vetorial por tabela. DISTANCE=cosinetem que bater com a função que você usa na consulta, senão o índice não é usado.
Passo 2: o que vira embedding
Erro clássico: gerar embedding só do nome. "Air Max 90" não diz nada ao modelo. Junte o que descreve o produto:
def texto_para_embedding(p):
return f"{p['nome']}. {p['categoria']}. {p['descricao']}"
Passo 3: carregar os produtos
import json, mariadb
from sentence_transformers import SentenceTransformer
modelo = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
con = mariadb.connect(host="127.0.0.1", user="root", password="senha", database="loja")
cur = con.cursor()
produtos = [
{"nome":"Tênis de corrida leve","categoria":"Calçados","preco":399.90,
"descricao":"Amortecimento macio, ideal para longas distâncias no asfalto."},
{"nome":"Fone Bluetooth com cancelamento de ruído","categoria":"Áudio","preco":899.00,
"descricao":"Bateria de 30 horas, ótimo para avião e escritório barulhento."},
{"nome":"Mochila impermeável 25L","categoria":"Acessórios","preco":249.90,
"descricao":"Compartimento para notebook de 15 polegadas, resiste à chuva."},
{"nome":"Garrafa térmica 1L","categoria":"Acessórios","preco":129.90,
"descricao":"Mantém café quente por 12 horas e água gelada por 24."},
]
for p in produtos:
vetor = modelo.encode(texto_para_embedding(p), normalize_embeddings=True)
cur.execute(
"INSERT INTO produtos (nome, descricao, categoria, preco, embedding) "
"VALUES (?, ?, ?, ?, VEC_FromText(?))",
(p["nome"], p["descricao"], p["categoria"], p["preco"], json.dumps(vetor.tolist())))
con.commit()
VEC_FromText() converte o texto [0.1, 0.2, ...] no formato binário da coluna. O inverso é VEC_ToText(), útil para depurar.
Passo 4: recuperar (o "R" do RAG)
def recomendar(pergunta, k=3):
v = modelo.encode(pergunta, normalize_embeddings=True)
cur.execute("""
SELECT nome, preco,
VEC_DISTANCE_COSINE(embedding, VEC_FromText(?)) AS distancia
FROM produtos
ORDER BY distancia
LIMIT ?""", (json.dumps(v.tolist()), k))
return cur.fetchall()
print(recomendar("algo para ouvir música no voo sem barulho"))
print(recomendar("bag for my laptop when it rains")) # inglês
print(recomendar("algo para llevar café caliente")) # espanhol
Repare: nenhuma das perguntas tem a palavra "fone", "mochila" ou "garrafa". E duas nem estão em português. Mesmo assim, o primeiro resultado deve ser o produto certo.
Passo 5: filtros de negócio
Similaridade não sabe que o cliente só tem R$ 300. Combine com SQL normal:
SELECT nome, preco
FROM produtos
WHERE preco <= 300
ORDER BY VEC_DISTANCE_COSINE(embedding, VEC_FromText(@v))
LIMIT 5;
Cuidado: com filtro muito seletivo, o índice aproximado pode trazer menos de K linhas, porque ele pega os vizinhos primeiro e o WHERE descarta depois. Se acontecer, aumente o LIMIT e corte na aplicação.
Entendendo o índice e a aproximação
Camada 2: A ─────────────── F (poucos nós, saltos longos)
Camada 1: A ──── C ──── F ──── H
Camada 0: A─B─C─D─E─F─G─H─I─J─K (todos os nós, vizinhos curtos)
busca entra no topo e "desce" chegando cada vez mais perto
- M (3 a 200, padrão 6): quantos vizinhos cada nó guarda. Maior = mais preciso, mais memória e carga mais lenta.
mhnsw_ef_search(variável de sessão): quantos candidatos olhar na busca. Maior = mais preciso e mais lento.
SET SESSION mhnsw_ef_search = 40;
Para conferir se o índice está sendo usado, rode um EXPLAIN e procure idx_emb. Para medir a precisão, compare o resultado com o de uma tabela sem índice (busca exata) numa amostra de perguntas.
Por que funciona em qualquer idioma
O banco não sabe português, inglês nem espanhol. Quem sabe é o modelo multilíngue: ele coloca frases de mesmo sentido em pontos próximos, independentemente do idioma. O MariaDB só mede distância. Se você usar um modelo treinado só em inglês, essa mágica some. Escolha do modelo é decisão de arquitetura.
E o resto do RAG?
Com os produtos em mãos, você monta o prompt: "Cliente pediu X. Produtos disponíveis: [lista]. Recomende e explique". Essa parte é do LLM e fica para outro dia. A parte difícil, que é achar o contexto certo, já está resolvida no banco.
Checklist antes da produção
- Guarde o nome e a versão do modelo junto dos dados.
- Recalcule o embedding sempre que nome ou descrição mudarem.
- Normalize os vetores e use sempre a mesma distância.
- Meça a precisão com perguntas reais dos seus clientes.
Leia também MariaDB Vector e o índice HNSW e, se seu banco ainda é MySQL, o guia de migração do MySQL para o MariaDB. Mais conteúdo em /mariadb.
Sobre o autor: Alexandre Almeida é MariaDB Ambassador no Brasil, autor do livro Aprendendo MySQL e trabalha com bancos de dados há mais de 30 anos.
Fontes: MariaDB Knowledge Base: Vector Overview, VECTOR data type, VEC_DISTANCE_COSINE, VEC_FromText e Vector System Variables (mariadb.com/kb). Documentação do sentence-transformers (modelo paraphrase-multilingual-MiniLM-L12-v2). Imagem: ilustração gerada por IA.