Usage & API Reference¶
Declaring a Vector Column¶
A vector column carries its index options: the index type, the dimension, the distance metric and the HNSW settings. There are two ways to declare it.
MYVECTOR(...) column type. The server-side query rewrite turns this DDL into a
binary column plus a comment. Plugin builds have the rewrite, and component builds have it
since #156 (not in v1.26.9 or earlier releases):
CREATE TABLE docs (
id INT PRIMARY KEY,
title VARCHAR(200),
category VARCHAR(32),
tenant_id INT,
published_at DATE,
embedding MYVECTOR(type=HNSW,dim=768,size=100000,dist=Cosine,M=16,ef=100)
);
Any build: a MYVECTOR COLUMN comment. Declare the options in the column comment
yourself. This works without the rewrite, so use it on component builds from v1.26.9 or
earlier:
CREATE TABLE docs (
id INT PRIMARY KEY,
title VARCHAR(200),
category VARCHAR(32),
tenant_id INT,
published_at DATE,
embedding VARBINARY(3100)
COMMENT 'MYVECTOR COLUMN type=HNSW,dim=768,size=100000,dist=Cosine,M=16,ef=100,idcol=id'
);
On MySQL 9.x you can use the native VECTOR(n) type with the same comment.
Index types¶
type= |
Index | Use it for |
|---|---|---|
KNN |
Brute-force exact search | Small tables, or as a reference to check recall |
HNSW |
HNSW graph (approximate) | Most workloads |
HNSW_BV |
HNSW on binary vectors, Hamming distance | Vectors built with myvector_construct(..., 'i=string,o=bv') |
type is case-insensitive (hnsw and HNSW both work). Other option names are
case-sensitive: write M=16, not m=16. See Known limitations.
Build parallelism¶
threads=N sets how many threads build the index. It defaults to the
myvector_index_bg_threads system variable (default 2, range 1-100):
Override it per column in the comment (threads=48) to build faster on a bigger
box without changing the server-wide default. See
myvector_index_build for the build call this controls.
The type must be spelled correctly¶
A misspelled or missing type is an error. MyVector does not build a brute-force KNN
index in its place:
-- Plugin DDL: rejected at CREATE TABLE
CREATE TABLE t (id INT PRIMARY KEY, v MYVECTOR(type=hnws,dim=3,size=100));
-- ERROR 1644 (HY000): MYVECTOR column type invalid: hnws (use KNN, HNSW or HNSW_BV)
-- Comment form: the table is created, but the index build fails
CREATE TABLE t (id INT PRIMARY KEY,
v VARBINARY(64) COMMENT 'MYVECTOR COLUMN type=hnws,dim=3,size=100,idcol=id');
CALL mysql.myvector_index_build('db.t.v', 'id');
-- ERROR: unknown index type 'hnws' for db.t.v. Use type=KNN, HNSW or HNSW_BV
-- Comment form with no type at all
CREATE TABLE t2 (id INT PRIMARY KEY,
v VARBINARY(64) COMMENT 'MYVECTOR COLUMN dim=3,size=100,idcol=id');
CALL mysql.myvector_index_build('db.t2.v', 'id');
-- ERROR: missing index type for db.t2.v. Use type=KNN, HNSW or HNSW_BV
MYVECTOR(...) without a type still defaults to KNN: the plugin writes type=KNN into
the comment for you. A hand-written comment must name the type, including type=KNN.
Upgrading
Before this check, a misspelled or missing type silently built a KNN index and the
build returned SUCCESS. If an index built on an older version fails with one of the
errors above, correct the comment (ALTER TABLE ... MODIFY ... COMMENT '...') and
rebuild it. For an online=Y column, see
Online index updates.
Long comments can span lines¶
A comment may continue on the next line, or use a tab after MYVECTOR COLUMN:
CREATE TABLE docs (
id INT PRIMARY KEY,
embedding VARBINARY(3100) COMMENT 'MYVECTOR COLUMN
type=HNSW,dim=768,size=100000,
dist=Cosine,M=16,ef=100,idcol=id'
);
The comment may also start with spaces or a line break before MYVECTOR COLUMN.
Write MYVECTOR COLUMN itself with a single space between the two words.
Check which index was built¶
myvector_index_status reports the type actually in use. Check it after the first
build:
CALL mysql.myvector_index_status('db.docs.embedding');
-- Vector Index : db.docs.embedding
-- Type : HNSW
-- Dimension : 768
-- Distance : Cosine
-- ...
Index Management¶
Build an Index:
Check Index Status:
Drop an Index:
Save and Load an Index:
-- Indexes are automatically saved. To load an index on startup:
CALL mysql.myvector_index_load('database.table.column');
Vector Functions¶
Create a Vector:
Calculate Distance:
SET @vec1 = myvector_construct('[1.2, 3.4, 5.6]');
SET @vec2 = myvector_construct('[1.0, 3.0, 5.0]');
SELECT myvector_distance(@vec1, @vec2, 'L2');
The metric is L2 (the default; EUCLIDEAN is the same), Cosine or IP, in any case.
If either vector is NULL, the result is NULL for that row. An unknown metric, or two
vectors of different dimensions, is an error that stops the statement.
Display a Vector:
Vector Search¶
Nearest neighbours (ANN): MYVECTOR_IS_ANN(index, key column, query vector, k)
returns the k rows nearest to the query vector (plugin builds, and component builds
that include #156; see Known limitations).
SET @q = myvector_construct('[1.2, 3.4, 5.6]');
SELECT id FROM t WHERE MYVECTOR_IS_ANN('db.t.v', 'id', @q, 10);
Search effort per query (HNSW): the options string accepts ef_search, the size of
the candidate list the HNSW search keeps. Higher values raise recall and cost time. It
applies to that query only. Queries without it use the index's own setting (the
ef_search column option, which defaults to ef).
-- nearest 10, searching harder than the index default
SELECT id FROM t WHERE MYVECTOR_IS_ANN('db.t.v', 'id', @q, 'nn=10,ef_search=400');
SELECT myvector_ann_set('db.t.v', 'id', @q, 'nn=10,ef_search=400');
Before #165 a query's ef_search was stored on the index. It then applied to every later
query from any session, and it raced with concurrent searches.
Filtered search: pass the keys of the rows that may be returned as a fifth
argument, usually a JSON_ARRAYAGG subquery. The search returns the k nearest rows
among those keys, so it returns k rows whenever at least k rows match.
SELECT id FROM t
WHERE MYVECTOR_IS_ANN('db.t.v', 'id', @q, 10,
(SELECT JSON_ARRAYAGG(id) FROM t WHERE category = 'books'));
If the filter matches no rows, the result is empty. For an HNSW index with 10,000 or fewer matching keys, MyVector computes exact distances over just those rows. For more matching keys, it walks the HNSW graph and skips rows that are not in the list.
Do not put the filter next to MYVECTOR_IS_ANN in the WHERE clause instead
(WHERE category = 'books' AND MYVECTOR_IS_ANN(..., 10)). That form finds the 10
nearest rows first and filters them afterwards, so it can return fewer than 10 rows.
Example: nearest books only. The docs table from
Declaring a Vector Column holds 100,000 rows in 20
categories, and 500 of them are books:
SET @q = myvector_construct('[...]'); -- the query embedding
-- Wrong: finds the 10 nearest rows of any category, then keeps the books among
-- them. Can return fewer than 10 rows, or none.
SELECT id FROM docs
WHERE category = 'books'
AND MYVECTOR_IS_ANN('db.docs.embedding', 'id', @q, 10);
-- Right: searches only the 500 books. Returns the 10 nearest books.
SELECT id FROM docs
WHERE MYVECTOR_IS_ANN('db.docs.embedding', 'id', @q, 10,
(SELECT JSON_ARRAYAGG(id) FROM docs WHERE category = 'books'));
The filter can be any query that returns keys, for example one tenant's rows, or rows the current user may see:
SELECT d.id, d.title
FROM docs d
WHERE MYVECTOR_IS_ANN('db.docs.embedding', 'id', @q, 10,
(SELECT JSON_ARRAYAGG(id) FROM docs
WHERE tenant_id = 42 AND published_at >= '2026-01-01'));
Building the key list reads every matching row. When the filter matches most of a large
table, that step costs more than the search. For those filters, use
MYVECTOR_ANN_FILTERED (next section).
Broad filters: MYVECTOR_ANN_FILTERED. This stored procedure takes the filter as a
WHERE predicate instead of a key list. It works on plugin and component builds.
Signature (not runnable SQL):
For example, the 10 nearest documents that are not archived, where most documents are not archived:
SET @q = myvector_construct('[...]');
CALL mysql.MYVECTOR_ANN_FILTERED('db.docs.embedding', 'id', @q, 10, 'archived = 0');
-- +-------+------------------------+
-- | id | distance |
-- +-------+------------------------+
-- | 91641 | 1.33261132240295400000 |
-- | 90597 | 1.40075933933258060000 |
-- | 82027 | 1.57868742942810060000 |
-- | ... | ... |
-- +-------+------------------------+
It returns up to k rows, nearest first: the key and the distance that
myvector_row_distance() reports. It works like this:
- It asks the index for the nearest candidates:
10 × k, and at least 100. - It keeps the candidates that pass the predicate.
- If fewer than
kpass, it asks for more candidates and repeats. If 10,000 candidates (the most one search returns) still hold fewer thankmatches, or the index has no more rows to return, it runs the key-list search above with the same predicate.
So it returns k rows whenever at least k indexed rows match.
When to use which: the key list costs a read of every matching row (a scan of the whole table if no index covers the filter). The procedure costs one or more rounds of candidates.
| The filter | Use |
|---|---|
| Matches most of the table (50%, 90%) | MYVECTOR_ANN_FILTERED. One round of candidates is usually enough. |
| Is selective and can use an index | The key list. It reads just the matching rows and computes exact distances over them. |
| Is selective but needs a table scan | Either. Measure on your data. |
For example, on a 300,000-row table with 16-dimensional vectors (k = 10, Docker on aarch64, so approximate): at 90% the key list took 179 ms and the procedure 4.5 ms. At 1% the key list took 57 ms with a table scan and 5.8 ms with an index on the filter column; the procedure took about 25 ms either way.
The predicate is SQL text, like the WHERE clause of a view. It can use any column of the
table, qualified by the table name if you like (docs.archived = 0). Write a quote inside
it as two single quotes:
CALL mysql.MYVECTOR_ANN_FILTERED('db.docs.embedding', 'id', @q, 10,
'category <> ''books'' AND tenant_id = 42');
The procedure is SQL SECURITY INVOKER, so the predicate runs with the privileges of the
user who calls it, like a query that user typed. The caller needs EXECUTE on the
procedure and SELECT on the table. Do not build the predicate from untrusted input.
The procedure returns a result set, so you cannot join it or use it inside another query. For that, use the key list.
You can also call the function directly, on any build. Component builds from v1.26.9 or
earlier have no MYVECTOR_IS_ANN rewrite, so this is the only way there. It returns a JSON
array of keys:
SELECT myvector_ann_set('db.t.v', 'id', @q, 'nn=10',
(SELECT JSON_ARRAYAGG(id) FROM t WHERE category = 'books'));
-- [412,87,1033,...]
The keys come back nearest first. To get the rows in that order, unpack the array with
JSON_TABLE in a derived table (JSON_TABLE does not accept the function call directly):
SELECT t.*
FROM (SELECT myvector_ann_set('db.t.v', 'id', @q, 'nn=10',
(SELECT JSON_ARRAYAGG(id) FROM t WHERE category = 'books')) AS js) src,
JSON_TABLE(src.js, '$[*]'
COLUMNS (rank_no FOR ORDINALITY, id BIGINT PATH '$')) nn
JOIN t ON t.id = nn.id
ORDER BY nn.rank_no;
Uninstalling the Component¶
Component builds remove MyVector with sql/myvector_uninstall_component.sql. It
drops the procedures, view and supplemental UDFs, then runs UNINSTALL COMPONENT.
UNINSTALL COMPONENT fails with ERROR 3540 while any other session that has run
a query since the install is still connected
(#155). That includes application
connection pools, an open mysql client, replicas, and MyVector's own binlog listener.
This is MySQL behaviour that the component can't work around, so check first:
CALL mysql.MYVECTOR_UNINSTALL_CHECK();
-- ID USER HOST DB COMMAND TIME STATE Note
-- 412 app 10.0.0.7:51234 shop Sleep 35 client session: blocks UNINSTALL if it has run a query
The uninstall script starts with CALL mysql.MYVECTOR_PREPARE_UNINSTALL(0). That
procedure checks for other client sessions first. If there are any, it raises an error
naming them, and the script stops before anything is dropped:
ERROR 1644 (45000): Other sessions may block UNINSTALL COMPONENT (#155): 412. See MYVECTOR_UNINSTALL_CHECK()
If there are none, it stops the binlog listener (MYVECTOR_BINLOG_STOP()) and the script
goes on. End the listed sessions and run the script again. Or, if you are sure, let
MyVector end them:
-- KILLs every other client session, including application connections and
-- replicas, then waits up to 10 s for them to end. Run UNINSTALL in the same session.
CALL mysql.MYVECTOR_PREPARE_UNINSTALL(1);
UNINSTALL COMPONENT 'file://myvector';
Clients that reconnect between the two statements block the uninstall again, so pause connection pools first.
Docker Compose¶
Local trial only
As with the Quick Start, this uses a fixed password with
the port bound to all interfaces. Fine for local trial use; use a real
secret and bind to 127.0.0.1 for anything beyond that.
version: '3.8'
services:
myvector:
image: ghcr.io/askdba/myvector:mysql8.4
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: myvector
MYSQL_DATABASE: vectordb
volumes:
- myvector-data:/var/lib/mysql
volumes:
myvector-data:
See Docker Images for the full list of available tags and Configuration for the full configuration reference.