强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

LevelDB 完全指南 / 第 6 章 · 自定义比较器

第 6 章 · 自定义比较器

6.1 默认比较器

LevelDB 默认使用 BytewiseComparator,即按字节逐位比较:

字典序比较规则:
  "apple" < "banana"    (因为 'a' < 'b')
  "abc"   < "abd"       (因为 'c' < 'd')
  "ab"    < "abc"       (短字符串 < 长字符串)
  "a\x00" < "a\x01"    (按字节值比较)
  "A"     < "a"         (大写字母 < 小写字母,因为 ASCII 码 65 < 97)

数字排序的问题

// 按字典序排列数字,结果可能不符合预期
db->Put(wopts, "key:1", "...");
db->Put(wopts, "key:2", "...");  
db->Put(wopts, "key:10", "...");
db->Put(wopts, "key:9", "...");

// 遍历顺序:key:1 → key:10 → key:2 → key:9  ← 字典序!
// 期望顺序:key:1 → key:2 → key:9 → key:10   ← 数值序

6.2 自定义比较器实现

C++ 实现:数值排序比较器

#include "leveldb/comparator.h"
#include "leveldb/db.h"
#include <cstring>
#include <cstdlib>

class NumericComparator : public leveldb::Comparator {
public:
    // 核心比较函数
    int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
        // 假设 key 格式为 "prefix:数字"
        int num_a = ExtractNumber(a);
        int num_b = ExtractNumber(b);

        if (num_a < num_b) return -1;
        if (num_a > num_b) return 1;

        // 数字相同时,按字节序比较(保证唯一性)
        return a.compare(b);
    }

    // 比较器名称(必须唯一,用于数据库兼容性检查)
    const char* Name() const override {
        return "NumericComparator";
    }

    // 以下方法可留空(高级优化)
    void FindShortestSeparator(std::string*, const leveldb::Slice&) const override {}
    void FindShortSuccessor(std::string*) const override {}

private:
    int ExtractNumber(const leveldb::Slice& key) const {
        std::string s = key.ToString();
        auto pos = s.rfind(':');
        if (pos == std::string::npos) return 0;
        return std::atoi(s.substr(pos + 1).c_str());
    }
};

int main() {
    NumericComparator cmp;
    leveldb::Options options;
    options.create_if_missing = true;
    options.comparator = &cmp;  // 使用自定义比较器

    leveldb::DB* db;
    leveldb::DB::Open(options, "/tmp/numeric_db", &db);

    db->Put(wopts, "item:10", "...");
    db->Put(wopts, "item:2", "...");
    db->Put(wopts, "item:1", "...");
    db->Put(wopts, "item:9", "...");

    auto* it = db->NewIterator(ropts);
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        std::cout << it->key().ToString() << std::endl;
    }
    // 输出: item:1 → item:2 → item:9 → item:10  ✅

    delete it;
    delete db;
}

Go 实现

type NumericComparer struct{}

func (c NumericComparer) Compare(a, b []byte) int {
    numA := extractNumber(a)
    numB := extractNumber(b)
    if numA < numB { return -1 }
    if numA > numB { return 1 }
    return bytes.Compare(a, b)
}

func (c NumericComparer) Name() string { return "NumericComparer" }

func (c NumericComparer) Separator(dst, a, b []byte) []byte { return nil }
func (c NumericComparer) Successor(dst, b []byte) []byte { return nil }

// 打开数据库时使用
db, err := leveldb.OpenFile("/tmp/db", &opt.Options{
    Comparer: NumericComparer{},
})

6.3 Key 设计最佳实践

原则一:可读前缀 + 业务标识

推荐的 Key 设计模式:

  {资源类型}:{资源ID}:{属性名}
  
示例:
  user:1001:name
  user:1001:email
  order:2026051001:status
  product:P001:price
  config:max_retry

原则二:时间戳 Key 需要补齐

// ❌ 错误:时间戳不补齐
std::string key = "log:" + std::to_string(timestamp);
// "log:1683724800" vs "log:9999999999"  → 字典序不等于时间序

// ✅ 正确:补零到固定长度
char key[32];
snprintf(key, sizeof(key), "log:%020ld", timestamp);
// "log:0000000001683724800" vs "log:0000000009999999999"

原则三:反向时间序 Key

// 场景:需要按时间从新到旧遍历
// 方法:使用 (MAX - timestamp) 作为 Key

uint64_t now = time(nullptr);
uint64_t reverse_ts = UINT64_MAX - now;

char key[32];
snprintf(key, sizeof(key), "rtlog:%020lu", reverse_ts);
// 遍历时 SeekToFirst 就是从最新开始

原则四:复合 Key 实现多维排序

// 场景:先按用户 ID 排序,再按时间排序
// Key 格式: user:{user_id:固定8字节}:{timestamp:固定8字节}

std::string MakeKey(uint32_t user_id, uint64_t timestamp) {
    std::string key(16, '\0');
    // 大端编码保证字典序 = 数值序
    key[0] = (user_id >> 24) & 0xFF;
    key[1] = (user_id >> 16) & 0xFF;
    key[2] = (user_id >> 8) & 0xFF;
    key[3] = user_id & 0xFF;
    key[4] = (timestamp >> 56) & 0xFF;
    key[5] = (timestamp >> 48) & 0xFF;
    // ... 依次填充
    return key;
}

6.4 Key 设计模式速查表

模式Key 格式用途遍历方式
简单 KVkey配置项单点查询
资源属性type:id:attr多字段实体前缀扫描
时间序列type:YYYYMMDDHHmmss日志/事件范围扫描
反向时间type:(MAX-ts)最新优先前缀扫描
复合索引idx:type:field1:field2多维查询前缀扫描
反转域名com.example.www域名存储前缀扫描

6.5 业务场景:电商订单系统

class OrderStore {
public:
    OrderStore(leveldb::DB* db) : db_(db) {}

    // 创建订单
    bool CreateOrder(uint64_t order_id, uint32_t user_id,
                     double amount, const std::string& status) {
        leveldb::WriteBatch batch;
        char ts[32];
        snprintf(ts, sizeof(ts), "%020lu", time(nullptr));

        // 主记录
        std::string main_key = "order:" + Uint64ToKey(order_id);
        std::string main_val = SerializeOrder(order_id, user_id, amount, status, ts);
        batch.Put(main_key, main_val);

        // 用户订单索引:支持按用户查询
        std::string user_idx = "idx:user_order:" + Uint32ToKey(user_id)
                              + ":" + Uint64ToKey(order_id);
        batch.Put(user_idx, "");

        // 时间索引:支持按时间查询
        std::string time_idx = "idx:time_order:" + std::string(ts)
                              + ":" + Uint64ToKey(order_id);
        batch.Put(time_idx, "");

        // 状态索引:支持按状态查询
        std::string status_idx = "idx:status:" + status
                                + ":" + Uint64ToKey(order_id);
        batch.Put(status_idx, main_val);

        return db_->Write(leveldb::WriteOptions(), &batch).ok();
    }

    // 查询用户的所有订单
    std::vector<uint64_t> GetUserOrders(uint32_t user_id, int limit = 20) {
        std::vector<uint64_t> orders;
        std::string prefix = "idx:user_order:" + Uint32ToKey(user_id) + ":";
        auto* it = db_->NewIterator(leveldb::ReadOptions());
        for (it->Seek(prefix);
             it->Valid() && it->key().starts_with(prefix) && orders.size() < limit;
             it->Next()) {
            uint64_t oid = KeyToUint64(it->key().ToString().substr(prefix.size()));
            orders.push_back(oid);
        }
        delete it;
        return orders;
    }

private:
    leveldb::DB* db_;

    std::string Uint32ToKey(uint32_t v) {
        std::string s(4, '\0');
        s[0] = (v >> 24); s[1] = (v >> 16);
        s[2] = (v >> 8);  s[3] = v;
        return s;
    }
    std::string Uint64ToKey(uint64_t v) {
        std::string s(8, '\0');
        for (int i = 7; i >= 0; i--) {
            s[i] = v & 0xFF; v >>= 8;
        }
        return s;
    }
    uint64_t KeyToUint64(const std::string& s) {
        uint64_t v = 0;
        for (int i = 0; i < 8; i++) v = (v << 8) | (uint8_t)s[i];
        return v;
    }
    std::string SerializeOrder(...) { /* JSON / Protobuf 序列化 */ return ""; }
};

6.6 注意事项

⚠️ 警告说明
比较器名称不可变打开已有数据库时,比较器的 Name() 必须与创建时一致
不可混用比较器用不同比较器打开同一数据库会导致数据损坏
性能敏感Compare() 会被频繁调用,避免复杂计算
一致性保证比较器必须满足严格弱序(Strict Weak Ordering)

6.7 本章小结

要点内容
默认比较器BytewiseComparator,字典序
自定义比较器继承 Comparator 接口,实现 Compare()Name()
Key 设计可读前缀、固定长度编码、复合索引
常见模式资源属性、时间序列、反向排序、多维索引

扩展阅读

  1. Comparator 接口include/leveldb/comparator.h
  2. BytewiseComparator 实现util/comparator.cc
  3. Key 编码设计:CockroachDB 的 Key 编码方案

第 5 章 · 批量写入 | 第 7 章 · 快照与 MVCC