---
title: "Búsqueda semántica en Laravel con MariaDB Vector, sin pgvector"
excerpt: "Laravel 13 soporta whereVectorSimilarTo sobre MariaDB 11.7 o superior, no solo sobre PostgreSQL con pgvector. Qué SQL genera, cómo se define la columna y el índice, y dónde se rompe la equivalencia entre los dos motores."
date: "2026-09-10T10:00:00.000Z"
category: "Bases de Datos"
tech_article: true
author:
  name: "angel cruz"
  picture: "/images/me/angel-cruz.png"
ogImage:
  url: "/images/open-graph/laravel-opengraph-image.png"
seo_title: "Laravel + MariaDB Vector: búsqueda semántica sin pgvector"
seo_description: "Cómo montar búsqueda semántica en Laravel con MariaDB Vector: columna VECTOR, VECTOR INDEX, whereVectorSimilarTo, el cast AsVector y las diferencias reales frente a pgvector."
---

**Laravel 13 hace búsqueda vectorial sobre MariaDB 11.7 o superior, no solo sobre PostgreSQL con la extensión `pgvector`.** El mismo `whereVectorSimilarTo` que ves en los tutoriales de Postgres funciona contra MariaDB, y el framework se encarga de traducir a `vec_distance_cosine()` en vez de al operador `<=>`.

Esto importa porque el stack real de mucha gente que trabaja con Laravel es MariaDB o MySQL, no Postgres. Cuando aparece un requisito de búsqueda semántica, la reacción típica es montar un Postgres al lado solo para los embeddings, o pagar un servicio de vectores. Con MariaDB 11.7 ninguna de las dos cosas es obligatoria.

Debajo están los requisitos exactos, el SQL que Laravel genera para cada motor, cómo se guarda el vector y en qué punto MariaDB y Postgres dejan de comportarse igual. En [las novedades de Laravel 13](/post/laravel-13-novedades) esto aparece mencionado de pasada y solo con pgvector.

## Requisitos

La documentación de búsqueda de Laravel 13 lo deja en tres piezas:

1. Una base de datos soportada: PostgreSQL con la extensión `pgvector`, MariaDB 11.7 o posterior, o MongoDB con el paquete oficial de Laravel.
2. El Laravel AI SDK (`laravel/ai`), que la búsqueda vectorial del query builder requiere porque los embeddings los genera la aplicación y no la base de datos.
3. Un proveedor de embeddings configurado en ese SDK.

En MariaDB, los vectores existen desde MariaDB Community Server 11.7 y desde MariaDB Enterprise Server 11.4.5-3. Si tu servidor es anterior, no hay nada que configurar: la funcionalidad no está en el binario.

## MySQL no entra en esta fiesta (y conviene saber por qué)

MySQL 9 tiene un tipo `VECTOR`, y ahí empieza la confusión, porque lo que falta es justamente la parte que necesitas.

En MySQL, una columna `VECTOR` guarda floats de 4 bytes, admite hasta 16383 entradas y por defecto son 2048. Pero no puede usarse como clave de ningún tipo (ni primaria, ni única, ni de particionado), y la función `DISTANCE()`, que es la que calcularía la similitud, está disponible solo para usuarios de MySQL HeatWave en OCI y de MySQL AI: no viene ni en la distribución Community ni en la Commercial.

Traducido: en MySQL comunitario puedes guardar el vector y no puedes buscar por él. Laravel es coherente con eso. La gramática base declara `supportsVectorDistance()` como `false` y solo MariaDB y PostgreSQL la sobrescriben, así que una consulta vectorial contra MySQL lanza una excepción explícita:

```
Vector distance queries are only supported by Postgres and MariaDB.
```

## La migración: columna VECTOR e índice

En MariaDB, el tipo se declara con el número de dimensiones fijo, hasta un máximo de 16383. Ese número lo decide tu modelo de embeddings: 1536 si usas `text-embedding-3-small` de OpenAI, por ejemplo.

Lo que escribes en Laravel es esto:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536)->index();
    $table->timestamps();
});
```

Ese `->index()` sobre una columna `vector` no crea un índice normal. El blueprint detecta que el tipo de columna es `vector` y redirige la llamada al método `vectorIndex`, que sabe emitir la sintaxis específica de cada motor.

Y no llames a `Schema::ensureVectorExtensionExists()`. Ese helper es de Postgres y solo de Postgres: si la conexión no es PostgreSQL, lanza una `RuntimeException` con el mensaje `Extensions are only supported by Postgres.`. En MariaDB no hay extensión que instalar, el tipo viene en el servidor.

### Qué SQL sale de ahí

En MariaDB, el índice se compila así:

```sql
alter table `documents` add vector index `documents_embedding_index`(`embedding`) M=6 DISTANCE=cosine
```

Ese `M=6 DISTANCE=cosine` no lo escribes tú, lo pone Laravel, y conviene saber por qué.

MariaDB permite construir el índice vectorial con distancia `euclidean` (que es su valor por defecto) o `cosine`, y con un parámetro `M` configurable entre 3 y 200 que regula el compromiso entre precisión, tamaño del índice y velocidad de inserción y consulta. Laravel elige `cosine` de forma fija, porque su método de consulta compara por similitud del coseno. Si creas el índice a mano con la distancia euclídea por defecto y luego consultas con Laravel, tienes una tabla con un índice que no sirve para la consulta que vas a lanzar.

En PostgreSQL, el mismo `->index()` genera un índice HNSW con la clase de operadores `vector_cosine_ops`: distinta sintaxis para la misma idea.

## El cast AsVector y por qué existe

En el modelo declaras el cast:

```php
use Illuminate\Database\Eloquent\Casts\AsVector;

protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}
```

El cast existe porque los dos motores devuelven y aceptan formatos distintos, y ese es el trozo que te costaría una tarde si lo montaras a mano.

Al leer, una columna vectorial de MariaDB devuelve bytes crudos: floats de 32 bits en little endian. El cast los desempaqueta con `unpack('g*', $value)`. PostgreSQL, en cambio, devuelve texto JSON, que se decodifica y se mapea a float.

Al escribir, PostgreSQL acepta el JSON directamente. MariaDB necesita que la conversión se haga en el servidor, así que el cast envuelve el valor en una expresión SQL `vec_fromtext('[...]')`, que es la función que documenta MariaDB para insertar vectores desde su representación en texto.

Con el cast puesto, tú sigues asignando y leyendo arrays de PHP y no te enteras de nada de esto.

## La consulta

Generar el embedding es responsabilidad de la aplicación. Con el AI SDK, para un texto suelto:

```php
use Illuminate\Support\Str;

$embedding = Str::of('Napa Valley tiene buen vino.')->toEmbeddings();
```

Y para un lote, que es lo que quieres al indexar contenido, porque va en una sola llamada al proveedor:

```php
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley tiene buen vino.',
    'Laravel es un framework PHP.',
])->generate();

$response->embeddings;
```

Con los vectores guardados, la consulta es la misma en MariaDB y en Postgres:

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();
```

Si en vez del vector le pasas una cadena, Laravel genera el embedding por ti, con caché activada, antes de construir la consulta:

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'las mejores bodegas de La Rioja')
    ->limit(10)
    ->get();
```

El umbral por defecto, si no pasas `minSimilarity`, es `0.6`. Va de `0.0` a `1.0`, donde `1.0` significa vectores idénticos. Por dentro, el método convierte esa similitud en distancia (`1 - $minSimilarity`), aplica `whereVectorDistanceLessThan` y añade un `orderByVectorDistance` que puedes desactivar con `order: false` cuando vayas a ordenar por otra columna.

### El SQL que genera cada motor

En MariaDB:

```sql
vec_distance_cosine(`embedding`, vec_fromtext(?))
```

En PostgreSQL con pgvector:

```sql
("embedding" <=> ?)
```

La misma llamada en PHP produce dos expresiones distintas, y son esas las que vas a ver en el log el día que tengas que depurar una consulta lenta.

Para más control hay tres métodos que trabajan con distancias en lugar de similitudes, y que puedes combinar:

```php
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

Y la búsqueda vectorial se combina con `where` normales, que es como se acota a un equipo, una categoría o un propietario:

```php
$documents = Document::query()
    ->where('team_id', $user->team_id)
    ->whereVectorSimilarTo('embedding', $request->input('query'))
    ->limit(10)
    ->get();
```

## MariaDB o PostgreSQL: cómo decidir

La respuesta corta es que la elección casi nunca la manda el vector.

Quédate en MariaDB si ya lo tienes en producción, tu servidor es 11.7 o posterior, y la búsqueda semántica es una funcionalidad más de una aplicación que ya vive ahí. Montar un segundo motor para una tabla de embeddings tiene un coste operativo permanente: otra copia de seguridad, otra conexión, otra cosa que se cae de madrugada.

Vete a PostgreSQL si ya lo usas, si necesitas afinar el índice HNSW con parámetros que MariaDB no expone del mismo modo, o si tu plataforma te lo da hecho. Los Postgres de Laravel Cloud vienen con `pgvector` instalado.

En MySQL comunitario no lo intentes, y no es una limitación de Laravel.

Lo que no cambia en ningún caso: los embeddings los genera tu aplicación llamando a un proveedor, y eso cuesta dinero y latencia en cada indexación y en cada búsqueda que no venga de la caché. La base de datos solo guarda y compara números.

## Preguntas frecuentes

### ¿Laravel soporta búsqueda vectorial en MariaDB?

Sí. Laravel 13 soporta consultas de similitud vectorial en PostgreSQL con la extensión `pgvector` y en MariaDB 11.7 o posterior, además de MongoDB con el paquete oficial. En los tres casos hace falta el Laravel AI SDK, porque los embeddings se generan en la aplicación.

### ¿Qué versión de MariaDB necesito para vectores?

MariaDB Community Server 11.7 o posterior. En la línea Enterprise, MariaDB Enterprise Server 11.4.5-3 o posterior. Antes de esas versiones el tipo `VECTOR` no existe.

### ¿Puedo usar whereVectorSimilarTo con MySQL?

No en MySQL Community ni Commercial. MySQL 9 tiene el tipo `VECTOR`, pero la función `DISTANCE()` solo está disponible en MySQL HeatWave sobre OCI y en MySQL AI. Laravel lo refleja lanzando una `RuntimeException` con el mensaje "Vector distance queries are only supported by Postgres and MariaDB".

### ¿Cuántas dimensiones admite una columna VECTOR en MariaDB?

Hasta 16383. El número lo fija el modelo de embeddings que uses, y tiene que coincidir con el que declares en la migración.

### ¿Qué distancia usa Laravel en MariaDB, coseno o euclídea?

Coseno. MariaDB usa distancia euclídea por defecto al crear un `VECTOR INDEX`, pero Laravel emite el índice con `DISTANCE=cosine` (y `M=6`) precisamente porque su consulta se compila a `vec_distance_cosine()`. Si creas el índice a mano, tiene que ser de coseno.

### ¿Tengo que llamar a Schema::ensureVectorExtensionExists() en MariaDB?

No, y además falla. Ese método es exclusivo de PostgreSQL: sobre cualquier otra conexión lanza `Extensions are only supported by Postgres.`. En MariaDB el soporte vectorial viene en el servidor y no hay extensión que habilitar.

### ¿Por qué hace falta el AI SDK si la base de datos ya calcula la distancia?

Porque la base de datos compara vectores, no textos. Convertir "las mejores bodegas de La Rioja" en un array de 1536 números es trabajo de un modelo de embeddings, y esa llamada la hace el SDK desde tu aplicación, tanto al indexar el contenido como al resolver cada búsqueda.

## Fuentes

- [Search, documentación de Laravel 13.x](https://laravel.com/docs/13.x/search), sección Semantic / Vector Search
- [Query Builder, documentación de Laravel 13.x](https://laravel.com/docs/13.x/queries), sección Vector Similarity Clauses
- [Código fuente de `laravel/framework`](https://github.com/laravel/framework), gramáticas `MariaDbGrammar` de consulta y de esquema, `Blueprint::vectorIndex` y el cast `AsVector`
- [Vector Overview](https://mariadb.com/docs/server/reference/sql-structure/vectors/vector-overview), documentación oficial de MariaDB
- [Tipo VECTOR](https://mariadb.com/docs/server/reference/sql-structure/vectors/vector), documentación oficial de MariaDB
- [The VECTOR Type](https://dev.mysql.com/doc/refman/9.4/en/vector.html) y [Vector Functions](https://dev.mysql.com/doc/refman/9.4/en/vector-functions.html), MySQL 9 Reference Manual

---

## Sitemap

Índice completo del sitio: [/sitemap.md](https://www.angelcruz.dev/sitemap.md)

Canónico HTML: [https://www.angelcruz.dev/post/laravel-mariadb-vector-busqueda-semantica](https://www.angelcruz.dev/post/laravel-mariadb-vector-busqueda-semantica)
