import os, sys, gc, time, tempfile, random
import numpy as np
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from typing import List, Dict
import sqlite3
import duckdb
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'apexbase', 'python'))
try:
from apexbase import ApexClient
HAS_APEX = True
except ImportError:
HAS_APEX = False
@dataclass
class TestConfig:
batch_size: int = 200_000
query_iters: int = 5
concurrent_threads: int = 8
ops_per_thread: int = 100
config = TestConfig()
def timer(func):
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - t0
return result, elapsed
return wrapper
def generate_data(n_rows: int) -> List[Dict]:
rng = np.random.default_rng(42)
cities = [f"City_{i}" for i in range(10)]
categories = [f"Category_{i}" for i in range(20)]
data = []
for i in range(n_rows):
data.append({
'_id': i + 1,
'user_id': int(rng.integers(1, 10000)),
'category': str(rng.choice(categories)),
'city': str(rng.choice(cities)),
'price': float(rng.uniform(10.0, 500.0)),
'quantity': int(rng.integers(1, 50)),
'is_active': bool(rng.choice([True, False], p=[0.8, 0.2]))
})
return data
def test_apexbase(data: List[Dict]) -> Dict:
if not HAS_APEX:
return {}
tmpdir = tempfile.mkdtemp()
try:
client = ApexClient(tmpdir)
client.create_table('test_table')
start = time.perf_counter()
batch_size = 20000
for i in range(0, len(data), batch_size):
batch = data[i:i+batch_size]
columns = {}
for key in batch[0].keys():
columns[key] = [record[key] for record in batch]
client.store(columns)
write_time = time.perf_counter() - start
queries = [
"SELECT COUNT(*) FROM test_table",
"SELECT city, COUNT(*) FROM test_table GROUP BY city",
"SELECT category, AVG(price) FROM test_table GROUP BY category",
"SELECT * FROM test_table WHERE price > 100 ORDER BY price DESC LIMIT 100"
]
query_times = []
for query in queries:
for _ in range(config.query_iters):
start = time.perf_counter()
result = client.execute(query)
try:
result.to_pandas()
except Exception:
pass
query_times.append(time.perf_counter() - start)
client.close()
return {
'write_speed': len(data) / write_time,
'avg_query_time': sum(query_times) / len(query_times),
'query_throughput': 1.0 / (sum(query_times) / len(query_times))
}
except Exception as e:
print(f" ApexBase 错误: {e}")
return {}
finally:
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
def test_sqlite(data: List[Dict]) -> Dict:
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "sqlite.db")
sqlite_data = []
for record in data:
r = dict(record)
r['id'] = r.pop('_id')
sqlite_data.append(r)
try:
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute('''
CREATE TABLE test_table (
id INTEGER PRIMARY KEY,
user_id INTEGER,
category TEXT,
city TEXT,
price REAL,
quantity INTEGER,
is_active INTEGER
)
''')
start = time.perf_counter()
conn.executemany('''
INSERT INTO test_table VALUES
(:id, :user_id, :category, :city, :price, :quantity, :is_active)
''', sqlite_data)
conn.commit()
write_time = time.perf_counter() - start
queries = [
"SELECT COUNT(*) FROM test_table",
"SELECT city, COUNT(*) FROM test_table GROUP BY city",
"SELECT category, AVG(price) FROM test_table GROUP BY category",
"SELECT * FROM test_table WHERE price > 100 ORDER BY price DESC LIMIT 100"
]
query_times = []
for query in queries:
for _ in range(config.query_iters):
start = time.perf_counter()
conn.execute(query).fetchall()
query_times.append(time.perf_counter() - start)
conn.close()
return {
'write_speed': len(data) / write_time,
'avg_query_time': sum(query_times) / len(query_times),
'query_throughput': 1.0 / (sum(query_times) / len(query_times))
}
except Exception as e:
print(f" SQLite 错误: {e}")
return {}
finally:
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
def test_duckdb(data: List[Dict]) -> Dict:
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "duckdb.duckdb")
try:
conn = duckdb.connect(db_path)
conn.execute('''
CREATE TABLE test_table (
_id INTEGER,
user_id INTEGER,
category VARCHAR,
city VARCHAR,
price DOUBLE,
quantity INTEGER,
is_active BOOLEAN
)
''')
start = time.perf_counter()
import pandas as pd
df = pd.DataFrame(data)
conn.execute('INSERT INTO test_table SELECT * FROM df')
write_time = time.perf_counter() - start
queries = [
"SELECT COUNT(*) FROM test_table",
"SELECT city, COUNT(*) FROM test_table GROUP BY city",
"SELECT category, AVG(price) FROM test_table GROUP BY category",
"SELECT * FROM test_table WHERE price > 100 ORDER BY price DESC LIMIT 100"
]
query_times = []
for query in queries:
for _ in range(config.query_iters):
start = time.perf_counter()
conn.execute(query).fetchall()
query_times.append(time.perf_counter() - start)
conn.close()
return {
'write_speed': len(data) / write_time,
'avg_query_time': sum(query_times) / len(query_times),
'query_throughput': 1.0 / (sum(query_times) / len(query_times))
}
except Exception as e:
print(f" DuckDB 错误: {e}")
return {}
finally:
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
def main():
print("\n" + "="*70)
print(" 三数据库性能对比测试")
print("="*70 + "\n")
print(f"测试配置:")
print(f" • 数据规模: {config.batch_size:,} 行")
print(f" • 查询迭代: {config.query_iters} 次")
print()
print("生成测试数据...")
data = generate_data(config.batch_size)
print(f" 数据量: {len(data):,} 条记录\n")
results = {}
print("─" * 50)
print("性能测试结果")
print("─" * 50)
if HAS_APEX:
print("测试 ApexBase...")
apex_result = test_apexbase(data)
results['ApexBase'] = apex_result
if apex_result:
print(f" 写入: {apex_result['write_speed']:,.0f} rows/sec")
print(f" 查询: {apex_result['avg_query_time']*1000:.1f}ms avg")
print(f" 吞吐: {apex_result['query_throughput']:.1f} queries/sec")
print("\n测试 SQLite...")
sqlite_result = test_sqlite(data)
results['SQLite'] = sqlite_result
if sqlite_result:
print(f" 写入: {sqlite_result['write_speed']:,.0f} rows/sec")
print(f" 查询: {sqlite_result['avg_query_time']*1000:.1f}ms avg")
print(f" 吞吐: {sqlite_result['query_throughput']:.1f} queries/sec")
print("\n测试 DuckDB...")
duckdb_result = test_duckdb(data)
results['DuckDB'] = duckdb_result
if duckdb_result:
print(f" 写入: {duckdb_result['write_speed']:,.0f} rows/sec")
print(f" 查询: {duckdb_result['avg_query_time']*1000:.1f}ms avg")
print(f" 吞吐: {duckdb_result['query_throughput']:.1f} queries/sec")
valid_results = {k: v for k, v in results.items() if v}
if not valid_results:
print("\n没有有效的测试结果")
return
print("\n─" * 50)
print("详细对比")
print("─" * 50)
print(f"\n{'数据库':<10} {'写入(rows/sec)':<15} {'查询延迟(ms)':<12} {'查询吞吐(q/s)':<15}")
print("-" * 55)
for db_name, result in valid_results.items():
write_speed = result['write_speed']
query_latency = result['avg_query_time'] * 1000
query_throughput = result['query_throughput']
print(f"{db_name:<10} {write_speed:<15,.0f} {query_latency:<12.1f} {query_throughput:<15.1f}")
print(f"\n─" * 50)
print("性能排名")
print("─" * 50)
write_ranking = sorted([(k, v['write_speed']) for k, v in valid_results.items()],
key=lambda x: x[1], reverse=True)
print(f"\n写入性能:")
for i, (db, speed) in enumerate(write_ranking, 1):
print(f" {i}. {db}: {speed:,.0f} rows/sec")
query_ranking = sorted([(k, v['query_throughput']) for k, v in valid_results.items()],
key=lambda x: x[1], reverse=True)
print(f"\n查询性能:")
for i, (db, throughput) in enumerate(query_ranking, 1):
print(f" {i}. {db}: {throughput:.1f} queries/sec")
print(f"\n─" * 50)
print("综合评估")
print("─" * 50)
if HAS_APEX and 'ApexBase' in valid_results and 'SQLite' in valid_results and 'DuckDB' in valid_results:
apex = valid_results['ApexBase']
sqlite = valid_results['SQLite']
duckdb_r = valid_results['DuckDB']
print(f"\nApexBase 相对性能:")
if apex['write_speed'] > sqlite['write_speed']:
ratio = apex['write_speed'] / sqlite['write_speed']
print(f" ✅ 写入速度是 SQLite 的 {ratio:.1f}x")
else:
ratio = sqlite['write_speed'] / apex['write_speed']
print(f" ❌ 写入速度是 SQLite 的 {1/ratio:.2f}x ({ratio:.1f}x slower)")
if apex['query_throughput'] > sqlite['query_throughput']:
ratio = apex['query_throughput'] / sqlite['query_throughput']
print(f" ✅ 查询性能是 SQLite 的 {ratio:.1f}x")
else:
ratio = sqlite['query_throughput'] / apex['query_throughput']
print(f" ❌ 查询性能是 SQLite 的 {1/ratio:.2f}x ({ratio:.1f}x slower)")
if apex['write_speed'] > duckdb_r['write_speed']:
ratio = apex['write_speed'] / duckdb_r['write_speed']
print(f" ✅ 写入速度是 DuckDB 的 {ratio:.1f}x")
else:
ratio = duckdb_r['write_speed'] / apex['write_speed']
print(f" ❌ 写入速度是 DuckDB 的 {1/ratio:.2f}x ({ratio:.1f}x slower)")
if apex['query_throughput'] > duckdb_r['query_throughput']:
ratio = apex['query_throughput'] / duckdb_r['query_throughput']
print(f" ✅ 查询性能是 DuckDB 的 {ratio:.1f}x")
else:
ratio = duckdb_r['query_throughput'] / apex['query_throughput']
print(f" ❌ 查询性能是 DuckDB 的 {1/ratio:.2f}x ({ratio:.1f}x slower)")
print(f"\n💡 推荐场景:")
if 'ApexBase' in valid_results and valid_results['ApexBase']['write_speed'] > 500000:
print(" 📝 ApexBase: 高吞吐写入场景")
if 'DuckDB' in valid_results and valid_results['DuckDB']['query_throughput'] > 100:
print(" 🔍 DuckDB: 复杂分析查询场景")
if 'SQLite' in valid_results:
print(" 🛡️ SQLite: 轻量级嵌入式场景")
if __name__ == "__main__":
main()