DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」

1. 一句话介绍:DuckDB 到底是什么

DuckDB 是一个纯 C++ 实现 的、进程内嵌入式分析型(OLAP)SQL 数据库,采用 MIT 开源协议。

  • 你肯定听说过 SQLite------它把数据库"装进"你的程序里,不用装服务器,一个文件就是一个库。SQLite 是**事务型(OLTP)**数据库,擅长"频繁地增删改查单条数据"。
  • DuckDB 就是 SQLite 的"分析型"亲戚 :同样不用装服务器、同样嵌入进程,但它专门为"对大量数据做聚合、统计、分组、连接"这类分析查询优化。

打个比方:SQLite 像"便利店收银机",每秒能处理很多笔小交易;DuckDB 像"数据分析师",给他一大箱数据,他能很快算出"平均、总和、分布",但让他一笔笔记流水账反而不太在行。

核心特征一览:

特征 说明
语言 纯 C++(C++17)实现
协议 MIT 开源,GitHub 20k+ Star,近年极活跃
部署方式 嵌入式,进程内运行,零服务器
存储 列式存储 + 向量化执行引擎
数据源 直接读 CSV / Parquet / JSON 等文件,也可以建表存数据
兼容 支持标准 SQL(JOIN、窗口函数、CTE、子查询等)

2. 为什么选它:使用优点盘点

2.1 零部署、零运维

DuckDB 是一个库(静态链接或动态链接),你的程序 include "duckdb.hpp" 就能用。不需要安装数据库服务、不需要启动守护进程、不需要配端口密码。程序启动即数据库启动,程序退出即数据库关闭。

2.2 快:列式存储 + 向量化执行

分析型查询的典型特点是"读很多行、算少数几列"。DuckDB 把数据按 存,配合向量化执行引擎 (一次处理一整批数据,而不是一行一行处理),在数据聚合场景下通常比 SQLite 快 数十倍到上百倍

2.3 直接查询文件,不用导入

你可以不建表,直接对磁盘上的 CSV / Parquet / JSON 文件执行 SQL:

sql 复制代码
-- 直接对 CSV 文件做 SQL 查询,就像查表一样
SELECT region, SUM(sales) FROM 'sales_2025.csv' GROUP BY region;

对做数据分析的人来说,这一步省掉了"先导入数据库"的繁琐流程。

2.4 标准 SQL 支持完善

JOIN、GROUP BY、HAVING、窗口函数(ROW_NUMBER / RANK 等)、CTE(WITH 语句)、子查询、正则、日期函数......DuckDB 支持得很完整,基本"会 SQL 就会用"。

2.5 并发读友好

DuckDB 是多线程并行查询的:一条大查询内部会自动拆成多个线程同时算,充分利用多核 CPU(见第 8 步的代码演示)。

2.6 活跃开源社区

MIT 协议、持续高频发版、文档完善、Python / C++ / Rust / Java 等语言都有官方绑定。用 C++ 集成时文档和示例都很齐全。


3. 什么时候用它:适用场景与避坑场景

3.1 适合的使用场景

场景 为什么合适
桌面/客户端软件内嵌数据分析 进程内运行,用户无需安装数据库
ETL 工具、数据清洗脚本 直接读 CSV/Parquet,SQL 写转换逻辑
本地大文件探索(GB~TB 级) 列式 + 向量化 + 多线程,比 pandas 单机快
微服务内嵌分析引擎 给服务加"本地 SQL 查询能力"
教学、实验、原型开发 零安装,几分钟跑起来
与 Python/R 配合做数据科学 C++ 侧做高性能查询,上层语言做可视化

3.2 不适合的场景(重要!)

场景 为什么不合适
高并发写入(比如电商订单流水) DuckDB 面向分析,单条写入性能与并发写不如 MySQL/PostgreSQL
需要数据库服务器、多进程共享 嵌入式设计,多个进程同时写同一文件存在限制
超低延迟点查(毫秒级单条查询) 分析引擎按批处理,单点查询不是它的强项

一句话记忆:DuckDB 是"算得快",不是"写得快"。


4. 快速上手:C++ API 具体使用方式

下面我们从零开始,一步一步在你的 C++ 程序里跑起 DuckDB。所有代码都带中文注释,可直接复制编译运行。

第 1 步:获取 DuckDB

有三种方式,任选其一(推荐第 1.1 种,最省事):

1.1 方式一:vcpkg 安装(推荐)
bash 复制代码
# 安装 duckdb 库
vcpkg install duckdb

# 编译时让 CMake 找到它
# 在你的 CMakeLists.txt 里加:
#   find_package(duckdb CONFIG REQUIRED)
#   target_link_libraries(你的目标 PRIVATE duckdb::duckdb)
1.2 方式二:下载官方 amalgamation 包

DuckDB 官方提供"单头文件 + 单源文件"的打包版本(amalgamation),下载解压后目录里有 duckdb.hpp 和 duckdb.cpp,直接把这两个文件拷进项目就能用:

复制代码
# 从官网 https://duckdb.org/docs/installation 下载 C++ 版
# 解压后你会得到类似:
#   duckdb.hpp
#   duckdb.cpp
1.3 方式三:源码编译
bash 复制代码
git clone --depth 1 https://github.com/duckdb/duckdb.git
cd duckdb
# 生成静态库 build/release/duckdb_static.lib(Windows)或 libduckdb_static.a(Linux/macOS)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_UNITTESTS=OFF
cmake --build build --config Release --target duckdb_static

第 2 步:创建数据库连接

DuckDB 的使用模型非常像 SQLite:一个数据库(DuckDB 对象)+ 一个或多个连接(Connection 对象)

  • DuckDB 相当于"数据库实例"(管理存储引擎)
  • Connection 相当于"一条会话通道"(执行 SQL)
cpp 复制代码
#include "duckdb.hpp"   // DuckDB 官方 C++ 头文件
#include <iostream>

int main() {
    // ========== 2.1 内存模式:数据库只在内存里,不落盘 ==========
    // 传入 nullptr 表示"纯内存数据库",速度最快,但程序退出数据就没了
    duckdb::DuckDB in_memory_db(nullptr);
    duckdb::Connection in_memory_con(in_memory_db);

    // ========== 2.2 文件模式:数据持久化到磁盘 ==========
    // 传入文件路径字符串,会在当前目录创建(或打开)一个数据库文件
    duckdb::DuckDB file_db("my_analysis.db");
    duckdb::Connection file_con(file_db);

    // 简单验证连接可用
    auto pong = file_con.Query("SELECT '连接成功!' AS msg");
    std::cout << pong->GetValue(0, 0).ToString() << std::endl;

    return 0;
}

类比理解:内存模式像"草稿纸",关了程序就没了;文件模式像"笔记本",写上去就长期保存。


第 3 步:执行第一条 SQL

接下来我们建一张表、插入几行数据、再查出来。这是最常用的"三步曲"。

cpp 复制代码
#include "duckdb.hpp"
#include <iostream>

int main() {
    duckdb::DuckDB db(nullptr);          // 内存模式
    duckdb::Connection con(db);          // 打开连接

    // ---------- 第 3.1 步:建表 ----------
    // CREATE TABLE:列名 + 类型(INTEGER 整数、VARCHAR 字符串、DOUBLE 小数)
    auto create = con.Query(
        "CREATE TABLE employees ("
        "  id INTEGER, "
        "  name VARCHAR, "
        "  department VARCHAR, "
        "  salary DOUBLE"
        ")");
    // ⚠️ 重要:每次 Query 都要检查是否有错误!
    if (create->HasError()) {
        std::cerr << "建表失败: " << create->GetError() << std::endl;
        return 1;
    }

    // ---------- 第 3.2 步:插入数据 ----------
    auto insert = con.Query(
        "INSERT INTO employees VALUES "
        "(1, '张伟', '研发', 25000), "
        "(2, '李娜', '市场', 18000), "
        "(3, '王强', '研发', 30000), "
        "(4, '赵敏', '人事', 15000)");
    if (insert->HasError()) {
        std::cerr << "插入失败: " << insert->GetError() << std::endl;
        return 1;
    }
    std::cout << "成功插入 " << insert->RowCount() << " 行" << std::endl;

    // ---------- 第 3.3 步:查询 ----------
    // 来一条"分析型"查询:按部门分组,算平均工资和最高工资
    auto result = con.Query(
        "SELECT department, "
        "       AVG(salary) AS avg_salary, "
        "       MAX(salary) AS max_salary "
        "FROM employees "
        "GROUP BY department "
        "ORDER BY avg_salary DESC");
    if (result->HasError()) {
        std::cerr << "查询失败: " << result->GetError() << std::endl;
        return 1;
    }

    // 打印表头(列名)
    std::cout << "部门\t平均工资\t最高工资" << std::endl;
    // 遍历每一行
    for (auto &row : *result) {
        std::cout << row.GetValue(0).ToString() << "\t"
                  << row.GetValue(1).ToString() << "\t"
                  << row.GetValue(2).ToString() << std::endl;
    }

    return 0;
}

编译运行方式(以 g++ 为例,假设 duckdb.hpp/duckdb.cpp 在当前目录,或已安装库):

bash 复制代码
# 方式 A:用官方 amalgamation 单文件包(最简单)
g++ -std=c++17 demo.cpp duckdb.cpp -o demo
./demo

# 方式 B:链接已编译好的静态库(Linux/macOS 示例)
g++ -std=c++17 demo.cpp -I/path/to/duckdb/include -L/path/to/duckdb/lib -lduckdb -o demo
./demo

# Windows 下用 MSVC 时记得加 duckdb_static.lib 或 duckdb.lib

预期输出

复制代码
成功插入 4 行
部门	平均工资	最高工资
研发	27500.0	30000.0
市场	18000.0	18000.0
人事	15000.0	15000.0

💡 小技巧:con.Query() 返回的 unique_ptr<MaterializedQueryResult>,用 ->HasError() 判断成功失败、->GetError() 拿错误信息、*result 迭代行、row.GetValue(列号) 取值。这是 DuckDB C++ API 最核心的套路。


第 4 步:读取查询结果

拿到结果后,除了 GetValue(列号),还有几种常用姿势:

cpp 复制代码
#include "duckdb.hpp"
#include <iostream>

int main() {
    duckdb::DuckDB db(nullptr);
    duckdb::Connection con(db);

    con.Query("CREATE TABLE t(a INTEGER, b VARCHAR)");
    con.Query("INSERT INTO t VALUES (1, '一'), (2, '二'), (3, '三')");

    auto result = con.Query("SELECT a, b FROM t ORDER BY a");

    // ---------- 4.1 获取列信息 ----------
    std::cout << "列数: " << result->ColumnCount() << std::endl;
    for (idx_t i = 0; i < result->ColumnCount(); i++) {
        std::cout << "第 " << i << " 列: " << result->GetColumnName(i)
                  << ",类型: " << result->GetTypes()[i].ToString() << std::endl;
    }

    // ---------- 4.2 按类型取值 ----------
    // 明确知道类型时,用模板方法 GetValue<类型>(列号, 行号)
    auto row0_a = result->GetValue<int32_t>(0, 0);      // 取第 0 行、第 0 列,转成 int32_t
    auto row0_b = result->GetValue<std::string>(1, 0);  // 取第 0 行、第 1 列,转成 string
    std::cout << "第一行: a=" << row0_a << ", b=" << row0_b << std::endl;

    // ---------- 4.3 遍历所有行(推荐写法) ----------
    for (auto &row : *result) {
        // GetValue(列号) 不带模板参数时,返回 duckdb::Value
        duckdb::Value v = row.GetValue(0);
        std::cout << "值=" << v.ToString()
                  << ",类型=" << v.type().ToString() << std::endl;
    }

    // ---------- 4.4 行数 ----------
    std::cout << "总行数: " << result->RowCount() << std::endl;

    return 0;
}

⚠️ 类型转换易错点:GetValue<int32_t>(...) 要求该列真实类型能安全转成 int32_t;如果列是 BIGINT(64 位)而你转成 int32_t,可能抛异常或丢失精度。拿不准时先用 Value 的 ToString() 打印看看。


第 5 步:直接查询 CSV / Parquet 文件

这是 DuckDB 的杀手锏:不建表、不导入,直接对文件跑 SQL

假设当前目录有一个 CSV 文件 sales.csv,内容长这样:

bash 复制代码
order_id,region,amount
1001,华北,520.5
1002,华东,880.0
1003,华北,310.2
1004,华南,999.9
cpp 复制代码
#include "duckdb.hpp"
#include <iostream>

int main() {
    duckdb::DuckDB db(nullptr);
    duckdb::Connection con(db);

    // ---------- 5.1 自动识别 CSV 结构并查询 ----------
    // read_csv_auto('路径'):自动推断列名、列类型,像查表一样用
    auto result = con.Query(
        "SELECT region, SUM(amount) AS total_sales, COUNT(*) AS order_cnt "
        "FROM read_csv_auto('sales.csv') "
        "GROUP BY region "
        "ORDER BY total_sales DESC");
    if (result->HasError()) {
        std::cerr << "查询 CSV 失败: " << result->GetError() << std::endl;
        return 1;
    }

    std::cout << "地区\t总销售额\t订单数" << std::endl;
    for (auto &row : *result) {
        std::cout << row.GetValue(0).ToString() << "\t"
                  << row.GetValue(1).ToString() << "\t"
                  << row.GetValue(2).ToString() << std::endl;
    }

    // ---------- 5.2 手动指定 CSV 结构(推荐生产环境用) ----------
    // 自动推断偶尔会猜错类型,手动指定更可靠
    auto result2 = con.Query(
        "SELECT * FROM read_csv_auto('sales.csv', "
        "  header=true, "                        // 第一行是表头
        "  columns={'order_id':'INTEGER',"       // 手动声明每列类型
        "           'region':'VARCHAR',"
        "           'amount':'DOUBLE'}) "
        "WHERE amount > 500");
    std::cout << "金额大于 500 的订单数: " << result2->RowCount() << std::endl;

    // ---------- 5.3 查询 Parquet 列式文件 ----------
    // 语法完全一样:read_parquet('xxx.parquet')
    auto parquet = con.Query(
        "SELECT COUNT(*) FROM read_parquet('big_data.parquet')");
    if (!parquet->HasError()) {
        std::cout << "Parquet 文件总行数: "
                  << parquet->GetValue(0, 0).ToString() << std::endl;
    }

    return 0;
}

💡 Parquet 是列式存储文件格式,和 DuckDB 是"天生一对"------查询时只需读需要的列,速度极快。你可以用 DuckDB 自己的 SQL 把 CSV 转成 Parquet:

sql 复制代码
COPY (SELECT * FROM read_csv_auto('sales.csv')) TO 'sales.parquet' (FORMAT PARQUET);

第 6 步:参数化查询 PreparedStatement

直接拼 SQL 字符串有注入风险 (比如用户输入的字符串里带引号),正确做法是用预处理语句:先"占位",再"绑值"。

cpp 复制代码
#include "duckdb.hpp"
#include <iostream>

int main() {
    duckdb::DuckDB db(nullptr);
    duckdb::Connection con(db);

    con.Query("CREATE TABLE products(id INTEGER, name VARCHAR, price DOUBLE)");
    con.Query("INSERT INTO products VALUES (1,'键盘',99), (2,'鼠标',49), (3,'显示器',1299)");

    // ---------- 6.1 创建预处理语句:用 ? 占位 ----------
    // 注意:这里只是"准备",还没有执行
    auto stmt = con.Prepare(
        "SELECT * FROM products WHERE price > ? AND name LIKE ?");

    // ---------- 6.2 绑定参数并执行 ----------
    // Execute(参数1, 参数2, ...):按 ? 从左到右的顺序传值
    auto result = stmt->Execute(50, "%鼠%");  // price > 50 且名字含"鼠"
    if (result->HasError()) {
        std::cerr << "执行失败: " << result->GetError() << std::endl;
        return 1;
    }

    std::cout << "命中 " << result->RowCount() << " 件商品:" << std::endl;
    for (auto &row : *result) {
        std::cout << "  " << row.GetValue(0).ToString() << " | "
                  << row.GetValue(1).ToString() << " | ¥"
                  << row.GetValue(2).ToString() << std::endl;
    }

    // ---------- 6.3 复用同一语句,换参数再查一次 ----------
    auto result2 = stmt->Execute(1000, "%显示%");  // price > 1000 且名字含"显示"
    std::cout << "第二次查询命中 " << result2->RowCount() << " 件商品" << std::endl;

    return 0;
}

⚠️ 常见坑:参数个数必须和 ? 个数一致,多传少传都会报错。另外 % 是 LIKE 的通配符,这里正好借它做模糊匹配。


第 7 步:Appender 批量写入

插入几万、几十万行时,用 INSERT 一条条执行太慢。DuckDB 提供 Appender(追加器),像"水管"一样把数据成批倒进表里,速度可以快几十倍。

cpp 复制代码
#include "duckdb.hpp"
#include <iostream>
#include <chrono>

int main() {
    duckdb::DuckDB db(nullptr);
    duckdb::Connection con(db);

    con.Query("CREATE TABLE sensor(id INTEGER, value DOUBLE, ts VARCHAR)");

    // ---------- 7.1 创建 Appender ----------
    // 参数:连接、模式(schema)、表名
    duckdb::Appender appender(con, "main", "sensor");

    // ---------- 7.2 批量追加数据 ----------
    auto start = std::chrono::steady_clock::now();
    const int N = 100000;  // 10 万行
    for (int i = 0; i < N; i++) {
        appender.BeginRow();        // 开始一行
        appender.Append<int32_t>(i);              // 第 1 列:id
        appender.Append<double>(i * 0.5);         // 第 2 列:value
        appender.Append<std::string>("2026-08-14 12:00:00"); // 第 3 列:ts
        appender.EndRow();          // 结束一行
    }
    appender.Close();  // ⚠️ 必须 Close/Flush,否则数据可能没真正写入!

    auto end = std::chrono::steady_clock::now();
    double seconds = std::chrono::duration<double>(end - start).count();
    std::cout << "批量写入 " << N << " 行,耗时 " << seconds << " 秒" << std::endl;

    // 验证一下
    auto cnt = con.Query("SELECT COUNT(*), SUM(value) FROM sensor");
    std::cout << "总行数=" << cnt->GetValue(0, 0).ToString()
              << ", 总值=" << cnt->GetValue(1, 0).ToString() << std::endl;

    return 0;
}

⚠️ 三个易错点:

  1. Append<T> 的列顺序和类型必须与建表时的列定义一致,否则会报错;
  2. 写完后必须调用 Close()(内部会 Flush),否则缓冲区的数据可能没落库;
  3. BeginRow() / EndRow() 必须成对出现。

第 8 步:多线程并发查询

DuckDB 单条查询内部自动多线程并行,同时它也允许多个线程各自开 Connection 并发查询。下面演示"生产者线程写数据,多个消费者线程同时查询"。

cpp 复制代码
#include "duckdb.hpp"
#include <thread>
#include <vector>
#include <iostream>

int main() {
    duckdb::DuckDB db(nullptr);

    // 主线程先准备一点数据
    {
        duckdb::Connection con(db);
        con.Query("CREATE TABLE logs(level VARCHAR, msg VARCHAR)");
        con.Query("INSERT INTO logs VALUES ('INFO','启动'), ('ERROR','磁盘满'), "
                  "('WARN','延迟高'), ('INFO','完成'), ('ERROR','连接超时')");
    }

    // ---------- 8.1 开 4 个线程并发查询 ----------
    std::vector<std::thread> threads;
    for (int t = 0; t < 4; t++) {
        threads.emplace_back([&db, t]() {
            // 每个线程独立开一个 Connection(⚠️ 不要跨线程共享同一个 Connection)
            duckdb::Connection con(db);
            auto result = con.Query(
                "SELECT level, COUNT(*) AS cnt FROM logs GROUP BY level ORDER BY cnt DESC");
            if (!result->HasError()) {
                std::cout << "[线程" << t << "] 查询结果: ";
                for (auto &row : *result) {
                    std::cout << row.GetValue(0).ToString() << "="
                              << row.GetValue(1).ToString() << " ";
                }
                std::cout << std::endl;
            }
        });
    }
    for (auto &th : threads) th.join();  // 等待所有线程结束

    return 0;
}

⚠️ 并发红线:Connection 不是线程安全的------每个线程必须创建自己的 Connection;而 DuckDB 数据库对象可以在多个线程间共享(只读使用没问题)。


5. 为什么它快:底层原理速览

既然主题是"使用",底层只讲三个关键点,帮你理解它快的本质:

  1. 列式存储(Columnar Storage) :传统行式数据库按"一行一行的记录"存,分析查询要扫描所有列;DuckDB 按"一列一列的数组"存,查询 SELECT region, SUM(amount) 时只读这两列,其他列完全不碰,I/O 大幅减少。
  2. 向量化执行引擎(Vectorized Execution):普通数据库逐行处理(一次处理 1 行),DuckDB 一次处理一批(例如一次处理 2048 行),CPU 缓存利用率更高,循环开销更小。
  3. 多线程并行(Morsel-Driven Parallelism):一条聚合查询会自动把数据切成很多小块,分给多个 CPU 核心并行算,最后合并结果。

一句话总结:"少读数据(列式)+ 批量计算(向量化)+ 多核并行",这就是 DuckDB 在分析场景下碾压传统行式数据库的秘密。


6. 易错环节 ⚠️ 预警清单

易错点 正确做法
⚠️ 1 忘记检查 HasError(),出错后程序莫名崩溃或结果为空 每次 Query() / Execute() 后都检查 ->HasError(),用 ->GetError() 打印原因
⚠️ 2 GetValue<T> 类型转换不匹配(如 BIGINT 转 int32) 先用 GetValue(列, 行) 拿 Value 打印 ToString() 确认类型
⚠️ 3 Appender 写完忘了 Close(),数据"神秘消失" 写完必须 Close()(内部会 Flush 缓冲)
⚠️ 4 Appender 的 Append<T> 顺序/类型与建表不一致 严格按列定义顺序、类型追加
⚠️ 5 内存模式(nullptr)以为数据会持久化 需要落盘就传文件路径创建 DuckDB("xxx.db")
⚠️ 6 多线程共用一个 Connection 每个线程独立 Connection,数据库对象可共享
⚠️ 7 预处理语句参数个数与 ? 不匹配 Execute 传参数量严格等于占位符数量
⚠️ 8 CSV 自动推断列类型猜错(日期/数字) 用 read_csv_auto('x.csv', header=true, columns={...}) 手动声明
⚠️ 9 编译链接失败(找不到 duckdb.hpp 或 -lduckdb) 确认 include 路径、库路径、库名(duckdb_static / duckdb)
⚠️ 10 拿它当高并发 OLTP 用,性能远不如预期 明确场景:DuckDB 是分析引擎,写密集场景请选传统数据库

7. 常见问题 FAQ 速查表

问题 快速答案
Q1:DuckDB 和 SQLite 有什么区别? SQLite 是行式 OLTP(擅长增删改单条);DuckDB 是列式 OLAP(擅长分析聚合)。两者都嵌入式、零服务器
Q2:需要安装数据库服务吗? 不需要,它是一个 C++ 库,直接链接进你的程序
Q3:数据存在哪里? 内存模式(nullptr)不落盘;文件模式(传路径)存成一个 .db 文件
Q4:怎么读 CSV / Parquet? SELECT * FROM read_csv_auto('a.csv') / read_parquet('b.parquet'),无需导入
Q5:插入大量数据很慢怎么办? 用 Appender 批量写入,比逐条 INSERT 快几十倍
Q6:怎么防止 SQL 注入? 用 con.Prepare("... WHERE x = ?") + stmt->Execute(值)
Q7:支持多线程吗? 支持:单条查询内部自动并行;多线程并发时每线程各建一个 Connection
Q8:支持哪些 SQL 特性? 标准 SQL 基本齐全:JOIN、窗口函数、CTE、子查询、GROUP BY 等
Q9:有 Python 版吗? 有官方 duckdb Python 包,C++ 和 Python 可以共用同一个数据库文件
Q10:商用可以吗? 可以,MIT 协议,自由使用(含商业用途)
Q11:能处理多大数据? 单机嵌入式场景下,常见用法可处理 GB~TB 级分析数据(受内存影响,也可配外部存储)
Q12:怎么转成 Parquet 加速后续查询? COPY (SELECT * FROM read_csv_auto('a.csv')) TO 'a.parquet' (FORMAT PARQUET);
相关推荐
NineData2 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean3 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean3 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定
知无不研3 小时前
lambda表达式的使用(3)
开发语言·c++·lambda
浪子明X3 小时前
从 MongoDB 文档到关系模型:构建可重跑、可对账的数据迁移流水线
数据库·mongodb·oracle
杜子不疼.3 小时前
国产数据库撑起固井软件自主化:金仓 × 中海油服「海恒 Cemsol」落地解析
数据库
隔窗听雨眠3 小时前
GBase 8s并发控制深度解析:封锁机制、隔离级别与死锁处理全攻略
服务器·数据库·oracle
刀客Doc4 小时前
即时零售不只是多一个渠道,品牌开始重做终端生意
大数据·数据库
就叫飞六吧4 小时前
防抖、幂等、唯一约束三件套 ;
数据库