
当业务场景涉及 “日增 1000 万条数据”(如日志采集、用户行为追踪、IoT 设备上报)时,传统 “单表 + 默认配置” 的数据库设计会迅速面临瓶颈:单表数据量超亿级后插入延迟从 1ms 飙升至 100ms,磁盘空间半年内耗尽,甚至出现 “锁表导致写入中断”。
本文针对 “日增千万级数据” 的核心诉求 ——极致插入效率(写入延迟 < 10ms)、低成本海量存储(支持年级数据留存)、基础查询可用(非实时复杂查询),从表结构、分表分库、存储引擎、参数调优等维度,提供完整的数据库设计方案。
设计前需先锚定业务场景的关键特征,避免 “为技术而技术”,不同场景的设计侧重点差异极大:
业务场景 | 数据特征 | 核心诉求 | 非核心诉求 |
|---|---|---|---|
日志采集(如 API 日志) | 写入密集(读少写多)、无更新、单条数据小(100-500 字节) | 插入效率优先、存储成本低 | 复杂查询(如多条件聚合)可容忍离线计算 |
用户行为追踪(如点击日志) | 写入密集、部分字段需更新(如会话结束标记)、数据中等(500-1KB) | 插入 + 轻量查询效率、数据保留 3-6 个月 | 无需事务强一致性 |
IoT 设备上报(如传感器数据) | 写入均匀(每秒~116 条)、数据结构固定、无更新 | 高可用(不丢数据)、插入稳定 | 支持按设备 ID / 时间范围查询 |
核心共性约束:
日增 1000 万数据,单表无法支撑(MySQL 单表超 2000 万行后,插入 / 查询性能断崖式下降),分表分库是必选项,需优先确定分表策略。
“日增千万数据” 多为时序数据(按时间顺序产生),按时间分表是最优选择,可灵活适配数据生命周期管理,常见分表粒度:
分表粒度 | 单表数据量 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
按天分表 | 1000 万条 | 日志、行为追踪(需按天查询 / 清理) | 单表数据量可控、过期清理高效(直接删表) | 表数量多(年 365 张),需中间件管理 |
按周分表 | 7000 万条 | IoT 数据(查询频率低) | 表数量少(年 52 张) | 单表数据量较大,查询略慢 |
按月分表 | 3 亿条 | 非实时归档数据(如历史账单) | 表数量极少 | 单表超亿级,插入后期性能下降 |
推荐方案:按天分表(优先),表名格式为biz_table_yyyyMMdd(如api_log_20251121),理由:
若单库写入压力过大(如峰值 5000 条 / 秒,MySQL 单库写入上限约 1 万条 / 秒),需按 “时间范围分库” 或 “哈希分库” 扩展:
手动管理分表分库复杂度极高,需借助中间件实现 “业务无感知”:
以 “API 日志表按天分表、按月分库” 为例,ShardingSphere-JDBC 配置(YAML):
spring: shardingsphere: datasource: # 配置3个分库(202511、202512、202601) names: db_log_202511,db_log_202512,db_log_202601 db_log_202511: # 2025年11月库 type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.10:3306/db_log_202511?useSSL=false username: root password: 123456 # 其他库配置省略... rules: sharding: tables: api_log: # 逻辑表名(业务代码中使用) actual-data-nodes: db_log_${yearmonth}.api_log_${yearmonthday} # 实际表名规则 database-strategy: # 分库策略(按年月分库) standard: sharding-column: created_time # 分库字段(时间戳) sharding-algorithm-name: db_inline # 分库算法 table-strategy: # 分表策略(按天分表) standard: sharding-column: created_time sharding-algorithm-name: table_inline sharding-algorithms: db_inline: # 分库算法:提取年月(如202511) type: INLINE props: algorithm-expression: db_log_${date_format(created_time, 'yyyyMM')} table_inline: # 分表算法:提取年月日(如20251121) type: INLINE props: algorithm-expression: api_log_${date_format(created_time, 'yyyyMMdd')}表结构设计直接影响插入速度和存储占用,需遵循 “最小字段、合适类型、少索引” 原则:
业务字段 | 推荐类型 | 避免使用 | 理由 |
|---|---|---|---|
时间戳(创建时间) | DATETIME (3)(毫秒级,占 5 字节) | TIMESTAMP(依赖时区)、VARCHAR | 毫秒级精度满足多数场景,比 TIMESTAMP 更稳定 |
设备 ID / 用户 ID | BIGINT(8 字节)或 CHAR (32)(UUID) | VARCHAR (64)(冗余) | BIGINT 比 UUID 存储更省空间,查询更快 |
状态标识(如成功 / 失败) | TINYINT(1 字节,0 = 失败,1 = 成功) | INT(4 字节)、VARCHAR | 状态值少,TINYINT 足够,省空间 |
日志内容 / 上报数据 | VARCHAR (1024)(按需调整)或 TEXT | LONGTEXT(无限制,易冗余) | 多数场景 1KB 内足够,TEXT 需额外磁盘空间 |
数值型指标(如耗时、大小) | INT(4 字节,需 > 20 亿用 BIGINT) | VARCHAR(无法参与数值计算) | 数值型支持聚合查询,存储更省空间 |
NULL 值会增加存储开销(需额外标记),且影响索引效率,所有字段尽量设默认值:
索引加速查询,但严重拖慢插入(每插入一条数据需更新所有索引),日增千万数据场景下,索引需 “极简”:
仅对 “必须按该字段查询” 的字段建索引,且优先选择 “过滤性好” 的字段:
若插入后短期内无需查询(如当天数据仅需次日统计),可延迟建索引:
-- 逻辑表结构(实际按天分表,表名api_log_20251121)CREATE TABLE api_log_20251121 ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', device_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备ID', api_url VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'API地址', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=失败,1=成功', cost_time INT NOT NULL DEFAULT 0 COMMENT '耗时(毫秒)', request_data VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '请求数据', created_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间(毫秒级)', PRIMARY KEY (id) COMMENT '主键聚簇索引' -- 凌晨延迟创建联合索引:idx_device_time (device_id, created_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API日志表(20251121)';不同数据库 / 存储引擎的写入性能差异极大,需优先选择 “写入优化型” 引擎:
[mysqld]# 存储引擎选择InnoDBdefault-storage-engine=InnoDB# 缓冲池大小:设为物理内存的50%-70%(如32GB内存设20GB),减少磁盘IOinnodb_buffer_pool_size=20G# 写入刷盘策略:每秒刷盘(平衡性能与安全性,避免掉电丢1秒内数据)innodb_flush_log_at_trx_commit=2# 批量插入缓冲区:调大至64MB,提升批量插入效率bulk_insert_buffer_size=64M# 日志文件大小:设为4GB,减少日志切换频率innodb_log_file_size=4G# 关闭自动提交:批量插入时手动控制事务,减少事务开销autocommit=0# 禁用查询缓存:写入密集场景查询缓存无效,反而增加开销query_cache_type=0PostgreSQL 原生对海量时序数据支持一般,需安装TimescaleDB(时序数据库扩展):
-- 1. 创建扩展CREATE EXTENSION IF NOT EXISTS timescaledb;-- 2. 创建基础表CREATE TABLE api_log ( id BIGINT NOT NULL, device_id BIGINT NOT NULL, api_url VARCHAR(256) NOT NULL, status TINYINT NOT NULL, cost_time INT NOT NULL, created_time TIMESTAMPTZ(3) NOT NULL DEFAULT NOW());-- 3. 转换为时序表,按天分块(自动分表)SELECT create_hypertable('api_log', 'created_time', chunk_time_interval => INTERVAL '1 day');-- 4. 开启压缩(对3天前的块自动压缩)ALTER TABLE api_log SET ( timescaledb.compress, timescaledb.compress_orderby = 'created_time ASC', timescaledb.compress_segmentby = 'device_id');SELECT add_compression_policy('api_log', INTERVAL '3 days');若数据无需关系约束(如日志、行为数据),可选择 NoSQL 数据库,写入性能更高:
即使表结构和引擎优化到位,写入方式不当仍会导致性能瓶颈,需优先采用 “批量写入”:
单条INSERT(如INSERT INTO api_log VALUES (1, ...), (2, ...))每次需建立连接、解析 SQL,效率极低,批量插入是提升写入效率的核心手段:
-- 批量插入1000条数据,手动控制事务START TRANSACTION;INSERT INTO api_log_20251121 (device_id, api_url, status, cost_time)VALUES (1001, '/api/login', 1, 50),(1002, '/api/pay', 0, 200),-- ... 省略998条 ...(2000, '/api/order', 1, 150);COMMIT;用 MyBatis 批量插入,避免手动拼接 SQL:
// Mapper接口public interface ApiLogMapper { @Insert("<script>" + "INSERT INTO api_log_${dateStr} (device_id, api_url, status, cost_time) " + "VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.deviceId}, #{item.apiUrl}, #{item.status}, #{item.costTime})" + "</foreach>" + "</script>") void batchInsert(@Param("list") List<ApiLogDO> list, @Param("dateStr") String dateStr);}// 业务代码:积累1000条批量插入@Servicepublic class ApiLogService { @Autowired private ApiLogMapper apiLogMapper; // 内存队列:临时缓存待插入数据 private final BlockingQueue<ApiLogDO> queue = new ArrayBlockingQueue<>(10000); // 定时任务:每1秒批量插入一次(或队列满1000条插入) @Scheduled(fixedRate = 1000) public void batchInsertTask() { List<ApiLogDO> batchList = new ArrayList<>(1000); queue.drainTo(batchList, 1000); // 从队列取最多1000条 if (!batchList.isEmpty()) { String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date()); apiLogMapper.batchInsert(batchList, dateStr); } } // 接收单条数据,加入队列 public void addLog(ApiLogDO logDO) { try { queue.put(logDO); // 队列满时阻塞,避免数据丢失 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("日志插入队列满"); } }}若业务无需 “写入成功” 同步返回(如日志采集),可采用 “异步写入” 进一步提升性能:
日增 500GB 数据,若不清理,半年后磁盘空间将超 90TB,需制定数据生命周期管理策略:
SELECT drop_chunks(interval '3 months', 'api_log');日增千万数据场景下,数据库不可用将导致数据丢失,需构建高可用架构:
这套方案可支撑 “日增 1000 万条数据” 的高效插入与存储,同时兼顾查询需求与成本控制,已在日志采集、IoT 上报等场景落地验证,插入延迟稳定在 5-10ms,存储成本降低 60% 以上。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。