编程 DuckDB 深度实战:从零构建高性能数据分析引擎,嵌入式 OLAP 的终极指南

2026-04-27 18:23:00 +0800 CST views 8

DuckDB 深度解析:嵌入式分析型数据库的革命,为什么它正在取代 SQLite 成为数据科学的首选

当数据分析遇上嵌入式数据库,DuckDB 用向量化执行引擎和零外部依赖的设计,重新定义了"轻量级 OLAP"的可能性。

一、背景:为什么我们需要 DuckDB?

1.1 SQLite 的辉煌与局限

SQLite 是地球上部署最广泛的数据库——超过 1 万亿个活跃实例运行在从智能手机到航天器的各种设备上。它的成功源于极致的简洁:单文件、零配置、无服务器进程、跨平台兼容。对于 OLTP(在线事务处理)场景,SQLite 几乎是无敌的。

但时代变了。

数据科学家、分析师和工程师们每天面对的是:

  • GB 级 CSV/Parquet 文件:Excel 打不开,Pandas 加载慢到怀疑人生
  • 复杂的分析查询:多表 JOIN、窗口函数、聚合分析,SQLite 的逐行执行模式在这种场景下性能暴跌
  • Python/R 生态的割裂:需要在数据库和数据分析工具之间反复倒腾数据

SQLite 是为事务处理设计的,它的 B-tree 存储引擎和逐行执行模型在分析型工作负载(OLAP)面前显得力不从心。

1.2 大数据工具的"杀鸡用牛刀"困境

另一边,PostgreSQL、Snowflake、BigQuery 等数据库虽然分析能力强,但它们:

  • 需要安装、配置、维护服务器进程
  • 数据导入导出繁琐
  • 对于个人分析任务来说太重了

有没有一种数据库,能像 SQLite 一样轻量、嵌入式,又能像 PostgreSQL 一样高效处理分析查询?

DuckDB 就是答案。

1.3 DuckDB 的诞生

DuckDB 由荷兰 Centrum Wiskunde & Informatica (CWI) 的研究团队于 2019 年开源。它的设计目标非常明确:

成为分析型领域的 SQLite——一个面向 OLAP 的嵌入式数据库,零外部依赖,单文件部署,但拥有列式存储和向量化执行引擎。

二、核心架构:为什么 DuckDB 这么快?

2.1 列式存储(Columnar Storage)

传统行式数据库(如 SQLite、MySQL)将数据按行存储:

行式存储:
[ID:1, Name:Alice, Age:30, Salary:5000]
[ID:2, Name:Bob, Age:25, Salary:6000]
[ID:3, Name:Charlie, Age:35, Salary:7000]

分析查询通常只涉及少数几列(比如 SELECT AVG(salary) FROM employees),但行式存储必须读取整行数据,大量 I/O 被浪费。

DuckDB 采用列式存储:

列式存储:
ID 列:    [1, 2, 3]
Name 列:  [Alice, Bob, Charlie]
Age 列:   [30, 25, 35]
Salary 列:[5000, 6000, 7000]

优势:

  • 只读需要的列AVG(salary) 只需读取 Salary 列,I/O 减少 75%
  • 更好的压缩:同一列的数据类型相同,压缩率远高于行式存储
  • CPU 缓存友好:连续内存访问,充分利用 CPU 缓存行

2.2 向量化执行引擎(Vectorized Execution)

SQLite 采用传统的逐行迭代(Tuple-at-a-time)执行模型:

# 伪代码:逐行执行
for row in table:
    if row.age > 30:
        sum += row.salary
        count += 1
result = sum / count

每处理一行都涉及函数调用、条件判断、状态更新,CPU 流水线频繁被打断。

DuckDB 采用向量化执行(Vectorized Execution):

# 伪代码:向量化执行
for batch in table.chunks(1024):  # 一次处理 1024 行
    mask = batch.age > 30         # SIMD 并行比较
    filtered = batch.salary[mask] # 批量筛选
    sum += filtered.sum()         # SIMD 聚合
    count += filtered.count()
result = sum / count

核心优势:

  • SIMD 指令加速:利用 CPU 的 AVX2/AVX-512 指令,一次处理 8-16 个数据元素
  • 减少虚函数调用:批量处理,函数调用开销被摊薄
  • CPU 缓存命中率高:数据局部性好,缓存未命中显著减少
  • 并行执行:查询计划自动拆分为并行任务,充分利用多核 CPU

2.3 查询优化器

DuckDB 拥有完整的基于成本的查询优化器:

-- 用户写的查询
SELECT d.name, AVG(e.salary)
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.join_date > '2023-01-01'
GROUP BY d.name
HAVING AVG(e.salary) > 5000;

优化器会自动:

  1. 谓词下推:将 WHERE 条件尽可能推到数据源
  2. JOIN 重排序:选择最优的 JOIN 顺序,小表驱动大表
  3. 统计信息驱动:基于数据分布选择最优执行计划
  4. 自适应执行:运行时根据实际数据特征调整策略

2.4 零拷贝数据交换

DuckDB 与 Python/R 的集成是零拷贝的:

import duckdb
import pandas as pd

# 从 Pandas DataFrame 查询,零拷贝
 df = pd.DataFrame({'a': [1, 2, 3], 'b': ['x', 'y', 'z']})
result = duckdb.query("SELECT * FROM df WHERE a > 1").to_df()

# 从 Arrow 表查询,零拷贝
import pyarrow as pa
arrow_table = pa.table({'a': [1, 2, 3], 'b': ['x', 'y', 'z']})
result = duckdb.query("SELECT * FROM arrow_table WHERE a > 1").to_arrow_table()

DuckDB 直接读取 Python/R 的内存数据结构,无需序列化/反序列化,这是传统数据库无法做到的。

三、实战:DuckDB 代码示例

3.1 基础安装与使用

# Python 安装
pip install duckdb

# 命令行工具
pip install duckdb-cli
import duckdb

# 创建内存数据库(类似 SQLite 的 :memory:)
con = duckdb.connect()

# 或创建持久化数据库文件
con = duckdb.connect('my_analytics.db')

# 执行 SQL
con.execute("""
    CREATE TABLE users (
        id INTEGER PRIMARY KEY,
        name VARCHAR,
        age INTEGER,
        city VARCHAR,
        signup_date DATE
    )
""")

# 插入数据
con.execute("""
    INSERT INTO users VALUES
    (1, 'Alice', 30, 'Beijing', '2023-01-15'),
    (2, 'Bob', 25, 'Shanghai', '2023-03-20'),
    (3, 'Charlie', 35, 'Beijing', '2023-02-10'),
    (4, 'Diana', 28, 'Shenzhen', '2023-05-05')
""")

# 查询
result = con.execute("SELECT * FROM users WHERE age > 25").fetchdf()
print(result)

3.2 直接查询 CSV/Parquet 文件

这是 DuckDB 最杀手级的特性——无需导入,直接查询文件:

import duckdb

con = duckdb.connect()

# 直接查询 CSV 文件
result = con.execute("""
    SELECT 
        department,
        COUNT(*) as employee_count,
        AVG(salary) as avg_salary,
        MAX(salary) as max_salary
    FROM 'employees.csv'
    WHERE hire_date >= '2023-01-01'
    GROUP BY department
    HAVING COUNT(*) > 5
    ORDER BY avg_salary DESC
""").fetchdf()

# 直接查询 Parquet 文件(列式存储,性能更佳)
result = con.execute("""
    SELECT 
        date_trunc('month', order_date) as month,
        SUM(amount) as total_revenue,
        COUNT(DISTINCT customer_id) as unique_customers
    FROM 'orders/*.parquet'  -- 支持通配符
    WHERE status = 'completed'
    GROUP BY month
    ORDER BY month
""").fetchdf()

# 多文件 JOIN
result = con.execute("""
    SELECT 
        o.order_id,
        c.customer_name,
        p.product_name,
        o.quantity * p.price as total
    FROM 'orders.parquet' o
    JOIN 'customers.parquet' c ON o.customer_id = c.id
    JOIN 'products.parquet' p ON o.product_id = p.id
    WHERE o.order_date >= '2024-01-01'
""").fetchdf()

3.3 与 Pandas 的深度集成

import duckdb
import pandas as pd

# Pandas DataFrame 直接作为表查询
orders_df = pd.read_csv('orders.csv')
customers_df = pd.read_csv('customers.csv')

# 在 SQL 中直接引用 DataFrame
result = duckdb.query("""
    SELECT 
        c.customer_name,
        COUNT(*) as order_count,
        SUM(o.amount) as total_spent
    FROM orders_df o
    JOIN customers_df c ON o.customer_id = c.id
    GROUP BY c.customer_name
    ORDER BY total_spent DESC
    LIMIT 10
""").to_df()

# 复杂分析:窗口函数
result = duckdb.query("""
    SELECT 
        customer_id,
        order_date,
        amount,
        SUM(amount) OVER (
            PARTITION BY customer_id 
            ORDER BY order_date 
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) as running_total,
        AVG(amount) OVER (
            PARTITION BY customer_id 
            ORDER BY order_date 
            ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
        ) as moving_avg_3
    FROM orders_df
""").to_df()

3.4 高级特性:CTE、递归查询、PIVOT

-- 公共表表达式 (CTE)
WITH monthly_stats AS (
    SELECT 
        date_trunc('month', order_date) as month,
        region,
        SUM(amount) as revenue,
        COUNT(*) as order_count
    FROM orders
    GROUP BY month, region
),
ranked_regions AS (
    SELECT 
        month,
        region,
        revenue,
        RANK() OVER (PARTITION BY month ORDER BY revenue DESC) as rank
    FROM monthly_stats
)
SELECT * FROM ranked_regions WHERE rank <= 3;

-- 递归查询:组织架构层级
WITH RECURSIVE org_hierarchy AS (
    -- 锚点:顶层管理者
    SELECT id, name, manager_id, 0 as level
    FROM employees
    WHERE manager_id IS NULL
    
    UNION ALL
    
    -- 递归:下属员工
    SELECT e.id, e.name, e.manager_id, oh.level + 1
    FROM employees e
    JOIN org_hierarchy oh ON e.manager_id = oh.id
)
SELECT * FROM org_hierarchy ORDER BY level, name;

-- PIVOT:行列转换
PIVOT (
    SELECT region, product_category, SUM(sales) as total_sales
    FROM sales_data
    GROUP BY region, product_category
)
ON product_category
USING SUM(total_sales);

3.5 性能对比:DuckDB vs SQLite vs Pandas

import time
import duckdb
import sqlite3
import pandas as pd
import numpy as np

# 生成 1000 万行测试数据
np.random.seed(42)
n = 10_000_000
df = pd.DataFrame({
    'id': range(n),
    'category': np.random.choice(['A', 'B', 'C', 'D', 'E'], n),
    'value': np.random.randn(n),
    'amount': np.random.randint(1, 1000, n)
})

# 测试 1:分组聚合
print("=== 分组聚合性能对比 ===")

# Pandas
start = time.time()
pandas_result = df.groupby('category').agg({
    'value': 'mean',
    'amount': ['sum', 'max']
})
pandas_time = time.time() - start
print(f"Pandas: {pandas_time:.3f}s")

# SQLite
sqlite_conn = sqlite3.connect(':memory:')
df.to_sql('data', sqlite_conn, index=False)
start = time.time()
sqlite_result = pd.read_sql_query("""
    SELECT category, AVG(value), SUM(amount), MAX(amount)
    FROM data
    GROUP BY category
""", sqlite_conn)
sqlite_time = time.time() - start
print(f"SQLite: {sqlite_time:.3f}s")

# DuckDB
duck_conn = duckdb.connect()
start = time.time()
duckdb_result = duck_conn.execute("""
    SELECT category, AVG(value), SUM(amount), MAX(amount)
    FROM df
    GROUP BY category
""").fetchdf()
duckdb_time = time.time() - start
print(f"DuckDB: {duckdb_time:.3f}s")

print(f"\nDuckDB 比 Pandas 快 {pandas_time/duckdb_time:.1f}x")
print(f"DuckDB 比 SQLite 快 {sqlite_time/duckdb_time:.1f}x")

# 测试 2:复杂 JOIN
print("\n=== 复杂 JOIN 性能对比 ===")

df1 = pd.DataFrame({
    'id': range(1_000_000),
    'key': np.random.choice(range(100_000), 1_000_000),
    'value1': np.random.randn(1_000_000)
})

df2 = pd.DataFrame({
    'key': range(100_000),
    'value2': np.random.randn(100_000)
})

# Pandas
start = time.time()
pandas_join = df1.merge(df2, on='key', how='inner')
pandas_join_time = time.time() - start
print(f"Pandas JOIN: {pandas_join_time:.3f}s")

# DuckDB
start = time.time()
duckdb_join = duck_conn.execute("""
    SELECT df1.*, df2.value2
    FROM df1
    JOIN df2 ON df1.key = df2.key
""").fetchdf()
duckdb_join_time = time.time() - start
print(f"DuckDB JOIN: {duckdb_join_time:.3f}s")

print(f"\nDuckDB JOIN 比 Pandas 快 {pandas_join_time/duckdb_join_time:.1f}x")

典型性能数据(仅供参考,实际因硬件和数据而异):

操作PandasSQLiteDuckDBDuckDB 加速比
分组聚合(1000万行)2.5s8.2s0.15s17x vs Pandas, 55x vs SQLite
JOIN(100万 x 10万)4.1s12.5s0.25s16x vs Pandas, 50x vs SQLite
窗口函数(1000万行)5.8s不支持0.35s17x vs Pandas
Parquet 读取3.2s不支持0.5s6x vs Pandas

3.6 扩展功能:HTTPFS、JSON、正则表达式

import duckdb

con = duckdb.connect()

# 安装 HTTPFS 扩展(查询远程文件)
con.execute("INSTALL httpfs;")
con.execute("LOAD httpfs;")

# 直接查询 S3 上的 Parquet 文件
result = con.execute("""
    SELECT 
        year,
        COUNT(*) as flight_count,
        AVG(departure_delay) as avg_delay
    FROM read_parquet('s3://my-bucket/flights/*.parquet')
    WHERE origin = 'JFK'
    GROUP BY year
    ORDER BY year
""").fetchdf()

# JSON 处理
con.execute("""
    CREATE TABLE events AS
    SELECT * FROM read_json_auto('events.jsonl')
""")

result = con.execute("""
    SELECT 
        json_extract_string(data, '$.user.id') as user_id,
        json_extract_string(data, '$.event_type') as event_type,
        json_extract_int(data, '$.timestamp') as timestamp
    FROM events
    WHERE json_extract_string(data, '$.event_type') = 'purchase'
""").fetchdf()

# 正则表达式
result = con.execute("""
    SELECT 
        email,
        regexp_extract(email, '^([^@]+)@(.*)$', 1) as username,
        regexp_extract(email, '^([^@]+)@(.*)$', 2) as domain
    FROM users
    WHERE regexp_matches(email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')
""").fetchdf()

四、架构深度剖析

4.1 存储格式

DuckDB 使用自定义的列式存储格式,同时支持多种外部格式:

DuckDB Native Format:
- 列式存储,每列独立压缩
- 支持多种压缩算法:RLE、Dictionary、Bit Packing、FSST
- 自适应压缩:根据数据特征自动选择最优算法

外部格式支持:
- CSV/TSV(自动类型推断)
- Parquet(零拷贝读取)
- JSON/JSONL(自动结构推断)
- Arrow(内存格式共享)
- Iceberg/Delta Lake(湖仓格式)

4.2 执行引擎Pipeline

SQL 查询
    ↓
Parser → AST
    ↓
Binder → 语义分析(类型检查、名称解析)
    ↓
Optimizer → 查询优化(重写、代价模型)
    ↓
Physical Planner → 物理执行计划
    ↓
Execution Engine → 向量化执行
    ↓
Result

关键优化技术:

  1. 谓词下推(Predicate Pushdown)

    -- 优化前
    SELECT * FROM (SELECT * FROM big_table) WHERE x > 10
    
    -- 优化后
    SELECT * FROM big_table WHERE x > 10  -- 在读取时就过滤
    
  2. 投影下推(Projection Pushdown)

    -- 优化前:读取所有列
    SELECT a, b FROM (SELECT * FROM parquet_file)
    
    -- 优化后:只读取 a, b 列
    SELECT a, b FROM parquet_file  -- Parquet 列式存储优势
    
  3. 自适应 radix hash JOIN

    • 小表:in-memory hash table
    • 大表:partitioned hash join, spilled to disk
  4. 并行 CSV/Parquet 解析

    • 文件分块,多线程并行解析
    • 自动检测分隔符、编码、类型

4.3 与 SQLite 的架构对比

特性SQLiteDuckDB
存储模型行式(B-tree)列式
执行模型逐行迭代向量化批量
事务类型OLTP 优化OLAP 优化
并发写入支持(表级锁)支持(MVCC)
外部依赖
部署方式嵌入式/单文件嵌入式/单文件
Python 集成需通过驱动原生零拷贝
窗口函数有限支持完整支持
Parquet 支持原生
适用场景事务处理、配置存储数据分析、ETL、报表

五、性能优化技巧

5.1 数据加载优化

import duckdb

con = duckdb.connect()

# 技巧 1:批量插入(比逐行快 100x)
con.execute("""
    CREATE TABLE data (id INTEGER, name VARCHAR, value DOUBLE)
""")

# 慢:逐行插入
# for row in rows:
#     con.execute("INSERT INTO data VALUES (?, ?, ?)", row)

# 快:批量插入
con.executemany("INSERT INTO data VALUES (?, ?, ?)", rows)

# 更快:从 Pandas/Arrow 直接导入
con.execute("INSERT INTO data SELECT * FROM pandas_df")

# 技巧 2:使用 COPY 加载大文件
con.execute("""
    COPY data FROM 'large_file.csv' 
    (DELIMITER ',', HEADER true, AUTO_DETECT true)
""")

# 技巧 3:创建持久化数据库时预分配
con.execute("PRAGMA memory_limit = '4GB'")
con.execute("SET threads = 8")  # 使用 8 线程

5.2 查询优化

-- 技巧 1: ANALYZE 收集统计信息
ANALYZE;

-- 技巧 2:创建索引(对点查有效,分析查询通常不需要)
CREATE INDEX idx_user_id ON orders(user_id);

-- 技巧 3:分区裁剪
SELECT * FROM read_parquet('data/*/*/part-*.parquet')
WHERE year = 2024 AND month = 3;  -- 只读取匹配的分区

-- 技巧 4:使用 EXPLAIN 查看执行计划
EXPLAIN ANALYZE
SELECT category, AVG(amount)
FROM orders
GROUP BY category;

-- 技巧 5:物化视图(缓存复杂查询结果)
CREATE MACRO monthly_summary() AS TABLE
    SELECT 
        date_trunc('month', order_date) as month,
        SUM(amount) as revenue
    FROM orders
    GROUP BY month;

5.3 内存管理

import duckdb

con = duckdb.connect()

# 设置内存限制
con.execute("PRAGMA memory_limit = '2GB'")

# 设置临时目录(溢出到磁盘)
con.execute("PRAGMA temp_directory = '/tmp/duckdb_temp'")

# 设置线程数
con.execute("SET threads = 4")

# 监控内存使用
con.execute("PRAGMA database_size").fetchall()

六、生产环境实践

6.1 与 Python 数据生态集成

import duckdb
import pandas as pd
import polars as pl

con = duckdb.connect()

# DuckDB ↔ Pandas(零拷贝)
df = con.query("SELECT * FROM table").to_df()

# DuckDB ↔ Polars(零拷贝)
df_polars = con.query("SELECT * FROM table").to_polars()

# DuckDB ↔ Arrow(零拷贝)
arrow_table = con.query("SELECT * FROM table").to_arrow_table()

# 在 Jupyter 中直接显示
con.query("SELECT * FROM table")

6.2 ETL Pipeline 示例

import duckdb
from datetime import datetime

def etl_pipeline():
    con = duckdb.connect('warehouse.db')
    
    # 1. 从多个源加载数据
    con.execute("""
        CREATE OR REPLACE TABLE raw_orders AS
        SELECT * FROM read_parquet('s3://bucket/orders/*.parquet')
    """)
    
    con.execute("""
        CREATE OR REPLACE TABLE raw_customers AS
        SELECT * FROM read_csv_auto('s3://bucket/customers.csv')
    """)
    
    # 2. 数据清洗和转换
    con.execute("""
        CREATE OR REPLACE TABLE cleaned_orders AS
        SELECT 
            order_id,
            customer_id,
            order_date,
            amount,
            status,
            CASE 
                WHEN amount < 0 THEN NULL 
                ELSE amount 
            END as cleaned_amount
        FROM raw_orders
        WHERE order_date >= '2023-01-01'
    """)
    
    # 3. 聚合计算
    con.execute("""
        CREATE OR REPLACE TABLE daily_metrics AS
        SELECT 
            date_trunc('day', order_date) as date,
            COUNT(*) as order_count,
            SUM(cleaned_amount) as revenue,
            COUNT(DISTINCT customer_id) as unique_customers,
            AVG(cleaned_amount) as avg_order_value
        FROM cleaned_orders
        WHERE status = 'completed'
        GROUP BY date
        ORDER BY date
    """)
    
    # 4. 导出结果
    con.execute("""
        COPY daily_metrics TO 'output/daily_metrics.parquet' 
        (FORMAT PARQUET, PARTITION_BY (year, month))
    """)
    
    print(f"ETL 完成于 {datetime.now()}")

if __name__ == '__main__':
    etl_pipeline()

6.3 与 dbt 集成

DuckDB 是 dbt(数据构建工具)的官方适配器之一:

# profiles.yml
my_project:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: '/path/to/my_duckdb.db'
      threads: 4
-- models/stg_orders.sql
WITH source AS (
    SELECT * FROM {{ source('raw', 'orders') }}
),
renamed AS (
    SELECT
        id as order_id,
        user_id as customer_id,
        created_at as order_date,
        amount,
        status
    FROM source
)
SELECT * FROM renamed

七、DuckDB 的局限性与适用场景

7.1 不适用场景

场景原因替代方案
高并发写入写入优化不如 OLTP 数据库PostgreSQL, SQLite
主从复制无内置复制机制PostgreSQL, MySQL
多用户并发事务锁粒度较粗真正的客户端-服务器数据库
全文搜索无内置全文索引SQLite FTS, Elasticsearch
地理空间查询扩展支持有限PostGIS

7.2 最佳适用场景

  1. 数据科学工作流:Jupyter Notebook 中的交互式分析
  2. ETL/ELT 管道:数据转换和清洗
  3. 本地数据分析:GB-TB 级数据的单机分析
  4. 测试和开发:快速原型验证
  5. 嵌入式分析:在应用中嵌入分析能力
  6. 数据湖查询:Parquet/Iceberg 文件的即席查询

八、总结与展望

8.1 DuckDB 的核心价值

DuckDB 解决了数据分析领域的一个关键痛点:如何在保持轻量级的同时获得强大的分析性能

它的成功不是偶然的:

  • 正确的定位:不做 OLTP 的 SQLite,而是 OLAP 的 SQLite
  • 零依赖哲学:和 SQLite 一样,单文件即可运行
  • 生态集成:与 Python/R 生态深度整合,零拷贝数据交换
  • 现代架构:列式存储 + 向量化执行 + 并行处理

8.2 未来展望

DuckDB 正在快速发展:

  • 云原生扩展:DuckDB-WASM 在浏览器中运行
  • 分布式执行:DuckDB 团队正在探索分布式查询
  • 更多数据源:Iceberg、Delta Lake、Hudi 等湖仓格式支持
  • 性能持续优化:更好的压缩、更快的 JOIN、自适应执行

8.3 给开发者的建议

如果你是数据工程师、数据科学家或分析师:

  1. 现在就开始用:DuckDB 已经生产就绪,社区活跃
  2. 替代 Pandas 处理大数据:当数据超过内存时,DuckDB 是更好的选择
  3. 简化 ETL 流程:用 SQL 替代复杂的 Python 数据处理代码
  4. 保持关注:DuckDB 正在快速演进,新特性层出不穷

DuckDB 不是来取代 SQLite 或 PostgreSQL 的,它填补了一个空白:嵌入式分析型数据库。在这个领域,它是目前最好的选择。


参考资源

推荐文章

Python设计模式之工厂模式详解
2024-11-19 09:36:23 +0800 CST
PHP来做一个短网址(短链接)服务
2024-11-17 22:18:37 +0800 CST
css模拟了MacBook的外观
2024-11-18 14:07:40 +0800 CST
Elasticsearch 聚合和分析
2024-11-19 06:44:08 +0800 CST
git使用笔记
2024-11-18 18:17:44 +0800 CST
CSS实现亚克力和磨砂玻璃效果
2024-11-18 01:21:20 +0800 CST
网站日志分析脚本
2024-11-19 03:48:35 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
Go语言中的`Ring`循环链表结构
2024-11-19 00:00:46 +0800 CST
Linux 网站访问日志分析脚本
2024-11-18 19:58:45 +0800 CST
使用 Git 制作升级包
2024-11-19 02:19:48 +0800 CST
filecmp,一个Python中非常有用的库
2024-11-19 03:23:11 +0800 CST
liunx宝塔php7.3安装mongodb扩展
2024-11-17 11:56:14 +0800 CST
imap_open绕过exec禁用的脚本
2024-11-17 05:01:58 +0800 CST
使用 Go Embed
2024-11-19 02:54:20 +0800 CST
Rust async/await 异步运行时
2024-11-18 19:04:17 +0800 CST
程序员茄子在线接单