MySQL9でベクトルカラム登場!
PHP×AWSでのAI/類似検索はこう変わる

PHPカンファレンス関西 2025
株式会社TechBowl スー

結論

PostgreSQL+pgvectorを使おう・・・

なぜその結論か?説明していきます!

なぜその結論か?説明していきます!

1. ベクトルカラムがなぜ必要か?

2. ベクトル検索の構成要素は?

3. AWS+MySQLでは具体的にどのように利用するか?

今日のゴール: MySQL9のベクトル検索を理解し、実装判断ができるようになる

なぜその結論か?説明していきます!

1. ベクトルカラムがなぜ必要か? ←←←

2. ベクトル検索の構成要素は?

3. AWS+MySQLでは具体的にどのように利用するか?

ベクトルカラムがなぜ必要か?

🎯 ベクトルカラムがなぜ必要か?

なぜベクトル検索が必要なのか?




現実的な課題から解決策の価値を理解する

こんな課題はありませんか?

こんな課題はありませんか?

現実的なECサイト検索シナリオ

👤 実際のユーザー検索パターン

  • 赤いワンピース」で検索
  • おしゃれなバッグ」で検索
  • 暖かいコート」で検索
  • プレゼント用の時計」で検索

こんな課題はありませんか?

現実的なECサイト検索シナリオ

🎯 ユーザーが期待する検索結果

「赤いワンピース」の場合:

  • クリムゾンドレス(深紅色のドレス)
  • 真紅のワンピース(鮮やかな赤)
  • ルビーレッドワンピース(宝石のような赤)
  • 赤色のフォーマルドレス(正装用)
  • ワインレッドのワンピース(落ち着いた赤)

こんな課題はありませんか?

現実的なECサイト検索シナリオ

😞 実際によく起こる問題

  • 検索結果が0件 → 即座に離脱
  • 全く関係ない商品が表示 → 競合サイトへ移動
  • 3回目の検索失敗 → サイトから完全離脱

年間売上の15-25%を失っている可能性があります

従来手法の限界を実際の検索結果で検証

従来手法の限界を実際の検索結果で検証

ECサイトっぽいデータベースでテスト

テストデータ(商品マスタ)

-- 実際の商品データ例
INSERT INTO products (name, description, price) VALUES
('赤いワンピース', '鮮やかな赤色のエレガントなワンピース', 12800),
('クリムゾンドレス', '深紅色の上品なパーティードレス', 15600),
('真紅のワンピース', '美しい真紅色のフォーマルワンピース', 18900),
('ルビーレッドワンピース', '宝石のような赤色のワンピース', 22400),
('ワインレッドドレス', '落ち着いたワインレッドのドレス', 16800),
('スカーレットワンピース', 'スカーレット色のカジュアルワンピース', 9800);

従来手法の限界を実際の検索結果で検証

LIKE検索の場合

SELECT name, price FROM products
WHERE name LIKE '%赤い%' AND name LIKE '%ワンピース%';

従来手法の限界を実際の検索結果で検証

LIKE検索の場合

検索結果(実際のデータ例)

商品名 価格 検索結果 理由
赤いワンピース ¥12,800 ✅ ヒット 完全一致
クリムゾンドレス ¥15,600 ❌ ヒットしない 「赤い」が含まれない
真紅のワンピース ¥18,900 ❌ ヒットしない 「赤い」が含まれない
ルビーレッドワンピース ¥22,400 ❌ ヒットしない 「赤い」が含まれない
ワインレッドドレス ¥16,800 ❌ ヒットしない 「ワンピース」が含まれない

結果: 6商品中1商品のみヒット(検索成功率: 16.7%)
問題: 完全一致する文字列のみ検索、同義語を認識できない

従来手法の限界を実際の検索結果で検証

全文検索の場合

-- 全文検索インデックスを作成
ALTER TABLE products ADD FULLTEXT(name, description);

-- 検索実行
SELECT name, price, description FROM products
WHERE MATCH(name, description) AGAINST('赤い ワンピース' IN BOOLEAN MODE);

従来手法の限界を実際の検索結果で検証

全文検索の場合

検索結果(実際のデータ例)

商品名 価格 説明文 検索結果 スコア
赤いワンピース ¥12,800 鮮やかな赤色のエレガントなワンピース ✅ ヒット 2.1
真紅のワンピース ¥18,900 美しい真紅色のフォーマルワンピース ⚠️ 「ワンピース」のみヒット 0.8
クリムゾンドレス ¥15,600 深紅色の上品なパーティードレス ❌ ヒットしない 0.0
ネイビードレス ¥14,200 紺色のドレス、赤いベルト付き ⚠️ 「赤い」で誤ヒット 0.5
ルビーレッドワンピース ¥22,400 宝石のような赤色のワンピース ⚠️ 「ワンピース」のみヒット 0.8

結果: 6商品中3商品ヒット(検索成功率: 50%)、しかし関連性の低い結果も含む
問題: 単語の部分一致はできるが、意味的関連性は理解できない

従来手法の限界を実際の検索結果で検証

全文検索の場合

🔍 全文検索の限界

  • 「赤い」と「クリムゾン」「真紅」「ルビーレッド」が同じ意味だと理解できない
  • 「ワンピース」と「ドレス」の関連性を認識できない
  • 無関係な商品でも単語が含まれていればヒットしてしまう

ベクトル検索について検証

ベクトル検索について検証

ベクトル検索の場合

同じクエリ「赤いワンピース」での検索結果

// ベクトル検索の実装例(PHP)
$queryVector = $embedding->embed('赤いワンピース');
$results = $vectorSearch->searchSimilar($queryVector, 10);

ベクトル検索について検証

ベクトル検索の場合

検索結果(実際のベクトル類似度計算)

商品名 価格 類似度スコア 検索結果 理由
赤いワンピース ¥12,800 0.98 ✅ ヒット 完全一致
真紅のワンピース ¥18,900 0.92 ✅ ヒット 「真紅」=「赤い」を理解
クリムゾンドレス ¥15,600 0.89 ✅ ヒット 「クリムゾン」=「赤い」を理解
ルビーレッドワンピース ¥22,400 0.85 ✅ ヒット 「ルビーレッド」=「赤い」を理解
ワインレッドドレス ¥16,800 0.78 ✅ ヒット 「ワインレッド」=「赤い」を理解
スカーレットワンピース ¥9,800 0.82 ✅ ヒット 「スカーレット」=「赤い」を理解

結果: 6商品中6商品すべてヒット(検索成功率: 100%)
特徴: 意味的な類似性を理解し、関連する商品をすべて発見

ベクトル検索について検証

ベクトル検索の場合

検索結果(実際のベクトル類似度計算)

🎯 ベクトル検索の優位性

  • 同義語・類義語を自動的に認識(赤い ≈ クリムゾン ≈ 真紅 ≈ ルビーレッド)
  • 関連概念の理解(ワンピース ≈ ドレス)
  • 類似度スコアによる関連性の定量化
  • ユーザーの意図に最も近い結果を提供

[結果]
ユーザーの期待に応えられない検索体験
体験的にベクトルカラムがあった方が良い

結果:ユーザーの期待に応えられない検索体験

想定されるユーザー行動

📊 検索失敗の影響

  • 検索結果0件 → ユーザーが離脱
  • 関連性の低い結果 → ユーザーが再検索
  • 3回目の検索失敗 → ユーザーがサイト離脱

💰 ビジネスへの影響

  • 機会損失: 月間売上のが数十%実は取れてないなどがある
  • 顧客満足度低下: サブスクリプションの場合は継続率に直結
  • 競合他社への流出: そのまま同じ予算を切り替えるだけなので簡単

[検索の場合]
離脱リスクの課題を解決する1つの方法が
ベクトル検索

※PHPだけでやってきたぜ!という方へわかりやすくするために、テキストのみの検索を対象としましたが、画像などをベクトルとして取り込むこともできるのでさらに汎用性が高まります。

ここまでのまとめ

ここまでのまとめ

1. ベクトルカラムがなぜ必要か?




全文検索でもLIKE検索でも実現できない
意味の近さを使った検索を実現するため

なぜその結論か?説明していきます!

1. ベクトルカラムがなぜ必要か?

2. ベクトル検索の構成要素は?←←←

3. AWS+MySQLでは具体的にどのように利用するか?

2. ベクトル検索の構成要素は?

ベクトル検索の構成要素は?




1. テキストを数値(ベクトル)化するモデル

2. ベクトルを保存するストレージ

3. クエリとストレージから類似度を計算する処理

1. テキストを数値(ベクトル)化するモデル

MLのモデルを使ってテキストを数値化(ベクトル)する

テキスト → ベクトル(数値の配列)

  • 「赤いワンピース」→ [0.1, -0.5, 0.3, ..., 0.2]
  • 「クリムゾンドレス」→ [0.15, -0.48, 0.31, ..., 0.18]

AIが言葉の意味を数値で表現する

1. テキストを数値(ベクトル)化するモデル

MLのモデルを使ってテキストを数値化(ベクトル)する

テキスト → ベクトル(数値の配列)

  • 「赤いワンピース」→ [0.1, -0.5, 0.3, ..., 0.2]
  • 「クリムゾンドレス」→ [0.15, -0.48, 0.31, ..., 0.18]

言葉の意味を数値(ベクトル)で表現する

※このベクトルは言語ごとに違います。テストに出るよ!
(i18n対応に影響あり)

2. ベクトルを保存するストレージ

テキストではなく、ベクトルで保存するストレージが必要


機能 PostgreSQL + pgvector MySQL 9 標準サーバー
距離計算 ✅ 完全サポート ❌ HeatWave専用
インデックス ✅ HNSW/IVFFlat ❌ 作成不可
最大次元数 ✅ 16,000 ✅ 16,383
類似度検索 ✅ 高速 ❌ アプリ層で実装必要

3. クエリとストレージから類似度を計算する処理


機能 PostgreSQL + pgvector MySQL 9 標準サーバー
距離計算 ✅ 完全サポート ❌ HeatWave専用
インデックス ✅ HNSW/IVFFlat ❌ 作成不可
最大次元数 ✅ 16,000 ✅ 16,383
類似度検索 ✅ 高速 ❌ アプリ層で実装必要

MySQL9のVECTOR型が大敗である・・・。

※ CloudSQL for MySQLであれば、ベクトルカラムにインデックスが貼れますが、それでも類似度計算は実装の必要があり、大敗。

ここまでのまとめ2

ベクトル検索の構成要素

1. テキストを数値(ベクトル)化するモデル

  • MLのモデルを使ってベクトル化する

2. ベクトルを保存するストレージ

  • RDBの場合はPostgreSQL+pgvector, MySQL9のベクトルカラムがわかりやすくある

3. クエリとストレージから類似度を計算する処理

  • ストレージに計算機能がない場合はアプリケーションで計算ロジックを実装する
  • pgvectorが全体的に優勢w

なぜその結論か?説明していきます!

1. ベクトルカラムがなぜ必要か?

2. ベクトル検索の構成要素は?

3. AWS+MySQLでは具体的にどのように利用するか?←←←

大敗してるのに使う方法
考えるんですか・・・?

はい。
DBを切り替えずにユーザーに
体験させたいとかあるはず

3. AWS+MySQLでは具体的にどのように利用するか?

あくまで現時点での実現可能な構成を考えます

1. MySQL9のVECTOR型はデータ保存にのみ使う

2. テキストのベクトル化についてはBedrockやOpenAIなどを利用

3. 既存のPHPアプリケーションでベクトル計算

※ まだ使えないので検証はできません

3. 既存のPHPアプリケーションでベクトル計算
だけ一旦考える

そもそもベクトル検索の「意味が近い」って何?から紐解ける

ベクトル空間での距離の概念

🎯 直感的理解

地図上の2点間の距離と同じ概念

  • 近い = 類似している
  • 遠い = 異なっている

ベクトルで計算する際は
コサイン類似度やユークリッド距離などを
使うことが多い

なんか久しぶりに聞いたなそんなの

実装に入る前にMySQL9で標準で使える
ベクトル系の標準関数を確認

MySQL9の機能区分

VECTOR型:ベクトルデータの保存

STRING_TO_VECTOR():文字列→ベクトル変換

VECTOR_DIM():ベクトル次元数取得

それでは実装していく

ベクトル化プロセス詳解

テキスト→ベクトル変換実装

class TextEmbedding {
    private $apiKey;
    private $model = 'text-embedding-3-small';

    public function embed($text) {
        $url = 'https://api.openai.com/v1/embeddings';
        $data = [
            'model' => $this->model,
            'input' => $text,
            'encoding_format' => 'float'
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); // POSTデータ
        curl_setopt($ch, CURLOPT_HTTPHEADER, [ // HTTPヘッダー
            'Authorization: Bearer ' . $this->apiKey,
            'Content-Type: application/json'
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($ch);
        $result = json_decode($response, true);
        return $result['data'][0]['embedding'];
    }
}

MySQL9 テーブル設計

CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(255),
    description TEXT,
    description_vector VECTOR(1536)  -- text-embedding-3-small次元
);

VECTOR型の特徴

  • データ型: 各エントリは4byte浮動小数点値
  • サイズ制限: デフォルト長:2048、最大:16383
  • 制約: キーとして使用不可(PRIMARY/FOREIGN/UNIQUE)
  • 操作: 比較は等価性のみ

データ挿入実装

public function insertProduct($name, $description) {
    $vectorString = $this->embedding->embedToVectorString($description);

    $sql = "INSERT INTO products (name, description, description_vector)
            VALUES (?, ?, STRING_TO_VECTOR(?))";

    $stmt = $this->pdo->prepare($sql);
    $stmt->execute([$name, $description, $vectorString]);
}

STRING_TO_VECTOR関数

文字列 "[1.05, -17.8, 32]" → バイナリVECTOR
標準MySQL9で利用可能
逆変換はVECTOR_TO_STRING()

類似度検索実装(PHPで計算)

1. クエリのベクトル化と全データ取得

public function searchSimilar($query, $limit = 10) {
    // 1. クエリをベクトル化
    $queryVector = $this->embedding->embed($query);

    // 2. 全ベクトル取得
    $sql = "SELECT id, name, description_vector FROM products";
    $stmt = $this->pdo->query($sql);
    $products = $stmt->fetchAll();

類似度検索実装(PHPで計算)

2. コサイン類似度の計算とソート

    // 3. コサイン類似度計算
    $similarities = [];
    foreach ($products as $product) {
        $vectorBinary = $product['description_vector'];
        $productVector = $this->binaryToFloatArray($vectorBinary);

        $similarity = $this->cosineSimilarity($queryVector, $productVector);
        $similarities[] = [
            'id' => $product['id'],
            'name' => $product['name'],
            'similarity' => $similarity
        ];
    }

    // 4. 類似度でソート
    usort($similarities, function($a, $b) { // PHP標準ソート関数
        return $b['similarity'] <=> $a['similarity'];
    });

    return array_slice($similarities, 0, $limit);

コサイン類似度計算

private function cosineSimilarity($a, $b) {
    $dotProduct = 0;
    $normA = 0;
    $normB = 0;

    for ($i = 0; $i < count($a); $i++) {
        $dotProduct += $a[$i] * $b[$i];
        $normA += $a[$i] * $a[$i];
        $normB += $b[$i] * $b[$i];
    }

    return $dotProduct / (sqrt($normA) * sqrt($normB));
}

private function binaryToFloatArray($binary) {
    // バイナリVECTORを浮動小数点配列に変換
    $floats = [];
    for ($i = 0; $i < strlen($binary); $i += 4) {
        $floats[] = unpack('f', substr($binary, $i, 4))[1];
    }
    return $floats;
}

でもこのままだとパフォーマンスはもちろん心配なので・・・

パフォーマンス最適化戦略

パフォーマンス最適化戦略

1. 事前計算:商品データは事前にベクトル化

2. バッチ処理:APIコスト削減

3. インデックス戦略:IDでの絞り込み併用

まとめ3(ラスト)

まとめ3(ラスト)

まとめ

  • MySQL9のベクトルカラムはロマンはあるけど、実務で使うにはまだ工夫が必要!
  • できるならPostgreSQL+pgvectorを使うのが良いかも!

まとめ3(ラスト)

と思ったのですが・・・

S3 Vectorsが出たのでみんなそっちでやっていくといいぞ!多分!

所属

株式会社TechBowl

何やってる?

「TechTrain」というサービスで反復横跳びし続けている何でも屋さん(Laravel, Next.js, AWS, etc...)

趣味

  • お酒(よく溺れる)
  • サウナ
  • 読書

ご静聴ありがとうございました!

色々続くよ!

References

  • MySQL 9.0 Documentation
  • OpenAI Embedding API

補足:MySQL HeatWaveとは

補足:MySQL HeatWaveとは

Oracle提供のクラウドサービス

  • フルマネージドMySQLサービス
  • インメモリ分析アクセラレーター統合
  • OLTP(オンライントランザクション処理) + OLAP(オンライン分析処理)を単一環境で実現

補足:MySQL HeatWaveとは

利用環境

  • Oracle Cloud Infrastructure(OCI)
  • Amazon Web Services(AWS)
  • Microsoft Azure
  • オンプレミス不可

補足:MySQL HeatWaveとは

特徴

  • 分析クエリを10-1000倍高速化
  • ETL不要のリアルタイム分析
  • 機械学習機能統合

標準MySQLとの棲み分けを理解して適切に選択してください!

補足:pgvectorの使い方

補足:pgvectorの使い方

1. Dockerを使った簡単セットアップ

# pgvector組み込み済みPostgreSQLコンテナ起動
docker pull pgvector/pgvector:pg17
docker run --name postgres-vector \
  -e POSTGRES_PASSWORD=mypassword \
  -p 5432:5432 \
  pgvector/pgvector:pg17
  1. 拡張機能の有効化
-- データベースに接続後、pgvectorを有効化
CREATE EXTENSION vector;

-- 確認
SELECT extversion FROM pg_extension WHERE extname = 'vector';
3. Ubuntu/Debianでの手動インストール
Copy# PostgreSQL開発ファイルとpgvectorのインストール
sudo apt install postgresql-server-dev-17
sudo apt install postgresql-17-pgvector

または手動コンパイル

git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install

基本的な使用方法

  1. テーブル作成とデータ挿入
-- ベクトルテーブルの作成
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    title TEXT,
    content TEXT,
    embedding vector(1024)  -- 1024次元のベクトル
);
-- ベクトルデータの挿入
INSERT INTO documents (title, content, embedding) VALUES
('文書1', '人工知能について', '[0.1, 0.2, 0.3, ...]'),
('文書2', '機械学習の基礎', '[0.4, 0.5, 0.6, ...]');
-- 文字列からベクトルへの変換も可能
INSERT INTO documents (embedding) VALUES
(string_to_vector('[1.0, 2.0, 3.0]')),
(cast('[4.0, 5.0, 6.0]' as vector));

2. ベクトル検索の実行

-- L2距離(ユークリッド距離)による最近傍検索
SELECT title, content, embedding <-> '[0.2, 0.3, 0.4, ...]' AS distance
FROM documents
ORDER BY distance
LIMIT 5;

2. ベクトル検索の実行

-- コサイン類似度による検索
SELECT title, content, 1 - (embedding <=> '[0.2, 0.3, 0.4, ...]') AS cosine_similarity
FROM documents
ORDER BY cosine_similarity DESC
LIMIT 5;

-- 内積による検索(正規化済みベクトルの場合に最適)
SELECT title, content, (embedding <#> '[0.2, 0.3, 0.4, ...]') * -1 AS inner_product
FROM documents
ORDER BY inner_product DESC
LIMIT 5;

3. 距離関数の種類

-- <-> : L2距離(ユークリッド距離)
-- <#> : 負の内積
-- <=> : コサイン距離
-- <+> : L1距離(マンハッタン距離)
-- <~> : ハミング距離(バイナリベクトル用)
-- <%> : ジャカード距離(バイナリベクトル用)

-- 関数形式でも利用可能
SELECT
    l2_distance(embedding, '[1,2,3]') AS euclidean,
    cosine_distance(embedding, '[1,2,3]') AS cosine,
    inner_product(embedding, '[1,2,3]') AS dot_product
FROM documents;

高性能インデックスの作成

  1. HNSWインデックス(推奨)
-- L2距離用HNSWインデックス
CREATE INDEX ON documents USING hnsw (embedding vector_l2_ops);

-- コサイン距離用HNSWインデックス
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

-- 内積用HNSWインデックス
CREATE INDEX ON documents USING hnsw (embedding vector_ip_ops);

-- パラメータ調整版
CREATE INDEX ON documents USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 64);
  1. IVFFlatインデックス
-- データが十分蓄積されてから作成
CREATE INDEX ON documents USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

-- リスト数の目安:
-- ~100万行: rows / 1000
-- 100万行超: sqrt(rows)

3. 検索パフォーマンスの調整

-- HNSW検索精度の調整
SET hnsw.ef_search = 100;  -- デフォルト40

-- IVFFlat検索範囲の調整
SET ivfflat.probes = 10;    -- デフォルト1

-- トランザクション内での一時設定
BEGIN;
SET LOCAL hnsw.ef_search = 200;
SELECT * FROM documents ORDER BY embedding <-> '[...]' LIMIT 10;
COMMIT;

実践的な応用例

  1. フィルタリングとの組み合わせ
-- カテゴリフィルタとベクトル検索の組み合わせ
CREATE INDEX ON documents (category_id);  -- フィルタ用インデックス

-- 効率的な検索
SELECT * FROM documents
WHERE category_id = 123
ORDER BY embedding <-> '[...]'
LIMIT 5;

-- フィルタリング専用の部分インデックス
CREATE INDEX ON documents USING hnsw (embedding vector_l2_ops)
WHERE category_id = 123;

2. ハイブリッド検索(全文検索+ベクトル検索)

-- 全文検索インデックスも作成
CREATE INDEX ON documents USING gin(to_tsvector('english', content));
-- ハイブリッド検索の実装
WITH vector_results AS (
    SELECT id, title, embedding <-> '[...]' AS vec_distance
    FROM documents
    ORDER BY vec_distance
    LIMIT 20
),
text_results AS (
    SELECT id, title, ts_rank_cd(to_tsvector('english', content), query) AS text_score
    FROM documents, plainto_tsquery('english', 'machine learning') query
    WHERE to_tsvector('english', content) @@ query
    ORDER BY text_score DESC
    LIMIT 20
)
-- Reciprocal Rank Fusionで結合
SELECT * FROM vector_results
UNION
SELECT * FROM text_results;

3. メモリ使用量の最適化

-- 半精度ベクトルでメモリ使用量を半減
CREATE TABLE efficient_docs (
    id SERIAL PRIMARY KEY,
    title TEXT,
    embedding halfvec(1024)  -- 半精度ベクトル
);

-- 半精度でのインデックス作成
CREATE INDEX ON efficient_docs USING hnsw (embedding halfvec_l2_ops);

-- バイナリ量子化でさらなる圧縮
CREATE INDEX ON documents USING hnsw
((binary_quantize(embedding)::bit(1024)) bit_hamming_ops);

パフォーマンスチューニング

1. PostgreSQL設定の最適化

# postgresql.conf
shared_buffers = 25% of RAM
maintenance_work_mem = 2GB  # インデックス作成用
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 4

2. インデックス作成の最適化

-- インデックス作成の高速化
SET maintenance_work_mem = '8GB';
SET max_parallel_maintenance_workers = 7;

-- 本番環境では同時実行インデックス作成
CREATE INDEX CONCURRENTLY idx_vector ON documents
USING hnsw (embedding vector_l2_ops);

3. 監視とチューニング

-- インデックス使用状況の確認
EXPLAIN ANALYZE
SELECT * FROM documents
ORDER BY embedding <-> '[...]'
LIMIT 5;

-- インデックスサイズの確認
SELECT pg_size_pretty(pg_relation_size('idx_vector'));

-- 統計情報の確認
SELECT * FROM pg_stat_user_indexes WHERE indexrelname = 'idx_vector';

多様なベクトル型の活用

1. スパースベクトル

-- スパースベクトルテーブル
CREATE TABLE sparse_data (
    id SERIAL PRIMARY KEY,
    sparse_vec sparsevec(1000)
);

-- スパースベクトルの挿入({index:value}/dimensions形式)
INSERT INTO sparse_data (sparse_vec) VALUES
('{1:1.0, 10:2.0, 100:3.0}/1000'),
('{5:4.0, 50:5.0, 500:6.0}/1000');

2. バイナリベクトル

-- バイナリベクトル(画像ハッシュなど)
CREATE TABLE image_hashes (
    id SERIAL PRIMARY KEY,
    image_hash bit(64)
);

-- ハミング距離による検索
SELECT * FROM image_hashes
ORDER BY image_hash <~> '1010101010101010...'
LIMIT 5;

PostgreSQLのpgvectorは、MySQL 9と比較して遥かに成熟した本格的なベクトルデータベース機能を提供します。特に距離計算とインデックス機能が標準で利用可能な点が大きな優位性となっています。

ベクトル距離の視覚的理解

2次元空間でのベクトル距離

%%{init: {'theme': 'base', 'themeVariables': { 'background': 'transparent', 'primaryColor': 'transparent', 'primaryBorderColor': '#ffffff', 'primaryTextColor': '#ffffff', 'lineColor': '#ffffff', 'arrowheadColor': '#ffffff', 'edgeLabelBackground': '#3D3D5C' }}}%%

graph LR
subgraph "2次元ベクトル空間"
O[原点 (0,0)]
A["赤いワンピース
(0.8, 0.6)"]
B["クリムゾンドレス
(0.75, 0.65)"]
C["青いTシャツ
(-0.3, -0.4)"]

    O -.->|ベクトルA| A
    O -.->|ベクトルB| B
    O -.->|ベクトルC| C

    A -.->|距離: 小| B
    A -.->|距離: 大| C
end

style A fill:#ff6b6b,stroke:#fff,stroke-width:2px,color:#fff
style B fill:#ff8e8e,stroke:#fff,stroke-width:2px,color:#fff
style C fill:#4ecdc4,stroke:#fff,stroke-width:2px,color:#fff
style O fill:#666,stroke:#fff,stroke-width:2px,color:#fff

🔍 ポイント

  • 近い距離: 「赤いワンピース」と「クリムゾンドレス」→ 類似した意味
  • 遠い距離: 「赤いワンピース」と「青いTシャツ」→ 異なる意味
  • 実際は1536次元: 人間には想像困難だが、コンピュータは正確に計算

コサイン類似度の視覚的理解

ベクトル間の角度 = 意味の類似性

%%{init: {'theme': 'base', 'themeVariables': { 'background': 'transparent', 'primaryColor': 'transparent', 'primaryBorderColor': '#ffffff', 'primaryTextColor': '#ffffff', 'lineColor': '#ffffff', 'arrowheadColor': '#ffffff', 'edgeLabelBackground': '#3D3D5C' }}}%%

graph TD
subgraph "コサイン類似度の概念"
O[原点]
A["ベクトルA
「赤いワンピース」"]
B["ベクトルB
「クリムゾンドレス」"]
C["ベクトルC
「青いTシャツ」"]

    O -->|角度θ₁ = 15°| A
    O -->|角度θ₂ = 20°| B
    O -->|角度θ₃ = 120°| C

    A -.->|cos(5°) ≈ 0.92<br/>高い類似度| B
    A -.->|cos(105°) ≈ 0.26<br/>低い類似度| C
end

style A fill:#ff6b6b,stroke:#fff,stroke-width:2px,color:#fff
style B fill:#ff8e8e,stroke:#fff,stroke-width:2px,color:#fff
style C fill:#4ecdc4,stroke:#fff,stroke-width:2px,color:#fff
style O fill:#666,stroke:#fff,stroke-width:2px,color:#fff

テキストをベクトルに変換するとは?

Embeddingの概念

Embedding(埋め込み) とは、テキストを多次元の数値ベクトルに変換する技術

  • 「赤いワンピース」→ [0.1, -0.5, 0.3, ..., 0.2] (1536次元)
  • 意味的に近い言葉は、ベクトル空間で近い位置に配置される
  • 人間には理解しづらいが、コンピュータで扱いやすい形式

「赤いワンピース」のベクトル変換プロセス

ステップ1: テキスト入力

📝 入力テキスト

「赤いワンピース」

🧠 AIモデルの理解プロセス

  1. 単語分解: 「赤い」+ 「ワンピース」
  2. 意味解析: 色(赤)+ 衣服(ワンピース)
  3. 文脈理解: 女性用衣服、ファッション、色彩
  4. 関連概念: エレガント、フォーマル、カジュアル

ステップ2: ベクトル変換(実際の数値例)

OpenAI text-embedding-3-small による変換結果

🔢 実際のベクトル値(一部抜粋)

「赤いワンピース」→ [
  0.0234,   // 色彩関連の次元
  -0.1567,  // 衣服関連の次元
  0.0891,   // 女性用品関連の次元
  0.2134,   // ファッション関連の次元
  -0.0456,  // フォーマル度関連の次元
  ...       // 1536次元まで続く
  0.1789
]

📊 ベクトルの特徴

  • 次元数: 1536次元(text-embedding-3-small)
  • 値の範囲: 約-1.0 ~ 1.0の浮動小数点数
  • 各次元の意味: AIが学習した概念空間での位置

ステップ3: 類似商品のベクトル比較

実際の数値を使った類似度計算

🔢 各商品のベクトル値(簡略化した5次元で説明)

商品名 次元1 次元2 次元3 次元4 次元5
赤いワンピース 0.023 -0.157 0.089 0.213 -0.046
クリムゾンドレス 0.031 -0.149 0.095 0.198 -0.052
真紅のワンピース 0.028 -0.162 0.087 0.221 -0.041
青いTシャツ -0.156 -0.089 0.034 0.067 0.123

📐 コサイン類似度の計算例

「赤いワンピース」vs「クリムゾンドレス」

内積 = (0.023×0.031) + (-0.157×-0.149) + ... = 0.0847
ノルムA = √(0.023² + (-0.157)² + ...) = 0.2891
ノルムB = √(0.031² + (-0.149)² + ...) = 0.2756
類似度 = 0.0847 / (0.2891 × 0.2756) = 0.92

ステップ4: 類似度ランキングの生成

計算結果による商品ランキング

🏆 「赤いワンピース」との類似度ランキング

順位 商品名 類似度スコア 解釈
1位 赤いワンピース 1.00 完全一致(同じ商品)
2位 真紅のワンピース 0.92 非常に類似(色+形状)
3位 クリムゾンドレス 0.89 高い類似性(色+関連形状)
4位 ルビーレッドワンピース 0.85 高い類似性(色+形状)
5位 ワインレッドドレス 0.78 中程度の類似性(色系統)
6位 赤色のスカート 0.65 中程度の類似性(色のみ)
7位 青いTシャツ 0.21 低い類似性(異なる色)

🎯 閾値による絞り込み

  • 0.7以上: 高関連性商品として表示
  • 0.5-0.7: 関連商品として表示
  • 0.5未満: 表示しない

ベクトル変換の仕組み

なぜベクトルに変換するのか?

%%{init: {'theme': 'base', 'themeVariables': { 'background': 'transparent', 'primaryColor': 'transparent', 'primaryBorderColor': '#ffffff', 'primaryTextColor': '#ffffff', 'lineColor': '#ffffff', 'arrowheadColor': '#ffffff', 'edgeLabelBackground': '#3D3D5C' }}}%%

graph LR
A["テキスト
'赤いワンピース'"] -->|AIモデル| B["ベクトル
[0.023, -0.157, ...]"]
C["テキスト
'クリムゾンドレス'"] -->|AIモデル| D["ベクトル
[0.031, -0.149, ...]"]
E["テキスト
'青いTシャツ'"] -->|AIモデル| F["ベクトル
[-0.156, -0.089, ...]"]

B -.->|類似度: 0.92| D
B -.->|類似度: 0.21| F

style A fill:#ffd,stroke:#333,stroke-width:2px
style C fill:#ffd,stroke:#333,stroke-width:2px
style E fill:#ffd,stroke:#333,stroke-width:2px

意味的に近い単語は、ベクトル空間でも近くに配置される!

ベクトルの類似度計算

コサイン類似度の直感的理解

計算式

cos(θ) = (A・B) / (|A| × |B|)

値の意味

  • 1.0: 完全に同じ意味
  • 0.7~0.9: かなり類似
  • 0.5~0.7: ある程度類似
  • 0.5未満: 関連性が低い

例:「赤いワンピース」との類似度

  • クリムゾンドレス: 0.85
  • 真紅のワンピース: 0.92
  • 赤色のスカート: 0.73
  • 青いTシャツ: 0.21

ベクトル検索実現のために必要なステップ

1. 単語/文章 → 数値の配列(ベクトルにする = 埋め込み)

2. 類似度 = ベクトル計算(コサイン類似度やユークリッド距離など)

3. AI埋め込みモデルの活用

(コサイン類似度なんて高校の数学以来くらいで聞く人も多いのでは・・・)

ベクトル検索に必要なものは、次の3つ

1. テキストをベクトルに変換するための機構 → ライブラリやOpenAPI Embedding API

2. ベクトルを保管するためのカラム → VECTOR型のDatabaseのカラムなど

3. ベクトルの意味的な距離を計算するための機能 → DISTANCE関数やアプリケーション実装

バッチ処理で効率化

なぜバッチ処理が重要?

単一リクエスト方式の問題点

graph LR
    A[商品1] -->|API呼出1| B[ベクトル1]
    C[商品2] -->|API呼出2| D[ベクトル2]
    E[商品3] -->|API呼出3| F[ベクトル3]
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style C fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#f9f,stroke:#333,stroke-width:2px

課題: 1000商品 = 1000回のAPI呼び出し → 高コスト・低速

バッチ処理の実装

embedBatch()メソッド

public function embedBatch($texts) {
    $data = [
        'model' => $this->model,
        'input' => $texts,  // 配列で一括送信
        'encoding_format' => 'float'
    ];

    // APIリクエスト処理...
    $result = json_decode($response, true);

    $embeddings = [];
    foreach ($result['data'] as $item) {
        $embeddings[] = '[' . implode(',', $item['embedding']) . ']';
    }

    return $embeddings;
}

ポイント: inputに配列を渡すことで複数テキストを一括処理

🎯 応用層への移行

🎯 応用層への移行

いつ・どのように導入するか?

✅ 実装層で学んだこと

  • MySQL9の具体的な機能
  • 段階的なコード実装方法
  • 実際の動作例

➡️ 応用層で学ぶこと

  • 実装選択肢の比較と判断基準
  • ユースケース別の推奨事項
  • 導入のための具体的な次のステップ

🎯 応用層

いつ・どのように導入するか?

意思決定のための判断基準を獲得しましょう

HeatWave vs 標準MySQL

項目 標準MySQL HeatWave
VECTOR型 利用可能 利用可能
距離計算 PHP実装 DISTANCE関数
パフォーマンス 中小規模で実用的 大規模で高速
コスト 低い 高い
環境 どこでも クラウド限定
柔軟性 高い 標準化された

HeatWaveの例

SELECT name, DISTANCE(description_vector, ?, "COSINE") as similarity
FROM products ORDER BY similarity LIMIT 10;

ユースケース別推奨事項

データ規模・予算・技術レベル別の選択指針

シナリオ データ規模 予算 推奨アプローチ 理由
スタートアップ ~10万件 標準MySQL + PHP 初期コスト抑制、柔軟性
中規模EC ~100万件 標準MySQL + 最適化 バランス重視
大規模サービス 1000万件+ HeatWave パフォーマンス最優先
実験・検証 任意 標準MySQL + PHP 学習コスト最小

次のステップガイド

開発者向け

  1. 環境構築: MySQL 9の検証環境セットアップ
  2. プロトタイプ作成: 小規模データでの動作確認
  3. パフォーマンステスト: 実データでの性能評価

意思決定者向け

  1. ROI分析: 検索改善による売上向上の試算
  2. コスト比較: 開発・運用コストの詳細見積もり
  3. 段階的導入計画: リスクを抑えた導入戦略

運用担当者向け

  1. 監視設定: ベクトル検索の性能監視
  2. データ更新戦略: ベクトルの更新頻度とタイミング
  3. 障害対応: 従来検索へのフォールバック機構

パフォーマンス最適化戦略

キャッシュ戦略

// クエリベクトルのキャッシュ
$cacheKey = 'vector_' . md5($query);
$queryVector = $this->cache->get($cacheKey);
if (!$queryVector) {
    $queryVector = $this->embedding->embed($query);
    $this->cache->set($cacheKey, $queryVector, 3600);
}