分库分表的中间件实现(简化版)

发布时间:2026/7/15 22:38:07
分库分表的中间件实现(简化版) 一、系统上线两年用户数据从 GB 涨到 TB查询从毫秒级退化到秒级。分库分表、读写分离、索引优化都做了但性能瓶颈依然明显。这时候才意识到关系型数据库不是万能的某些场景需要引入 NoSQL 或 NewSQL。数据库选型的本质是在一致性、可用性、性能、扩展性之间做权衡。不同业务场景需要不同的数据库方案混合持久化Polyglot Persistence才是现代架构的常态。二、数据库技术选型的多维对比现代数据库已经分化为多个流派每个流派都有其适用的场景和权衡graph TB A[数据库选型] -- B[关系型数据库br/MySQL/PostgreSQL] A -- C[文档数据库br/MongoDB/Couchbase] A -- D[键值数据库br/Redis/DynamoDB] A -- E[列族数据库br/Cassandra/HBase] A -- F[搜索引擎br/Elasticsearch] A -- G[图数据库br/Neo4j] A -- H[时序数据库br/InfluxDB/TimescaleDB] A -- I[NewSQLbr/TiDB/CockroachDB] B -- B1[强一致性br/复杂查询] C -- C1[灵活Schemabr/高吞吐写] D -- D1[超低延迟br/简单查询] E -- E1[海量数据br/高可用] F -- F1[全文搜索br/复杂聚合] G -- G1[关系遍历br/路径查询] H -- H1[时序数据br/降采样] I -- I1[水平扩展br/强一致性] style B fill:#e1f5fe style I fill:#f3e5f5关系型数据库MySQL/PostgreSQL适合需要强一致性和复杂查询的场景。优势ACID 事务、SQL 标准、成熟生态。劣势水平扩展难、海量数据性能差。文档数据库MongoDB适合 Schema 灵活、写多读少的场景。优势灵活 Schema、水平扩展、高性能写。劣势一致性弱、复杂查询性能差、事务支持有限。键值数据库Redis适合超低延迟、简单查询的场景。优势毫秒级延迟、高并发、丰富数据结构。劣势存储成本高、查询能力弱、数据持久化复杂。列族数据库Cassandra适合海量数据、高可用的场景。优势线性扩展、高可用、写性能强。劣势一致性弱、查询灵活性差、运维复杂。搜索引擎Elasticsearch适合全文搜索、复杂聚合的场景。优势全文搜索、近实时、分布式。劣势事务不支持、数据一致性弱、存储成本高。图数据库Neo4j适合关系遍历、路径查询的场景。优势关系查询性能高、图算法支持。劣势不适用于非图场景、扩展性有限。时序数据库InfluxDB适合时序数据、监控场景。优势时序数据优化、降采样、数据压缩。劣势通用查询性能差、不适合事务。NewSQLTiDB试图结合关系型数据库的 ACID 和 NoSQL 的水平扩展。优势水平扩展、强一致性、兼容 MySQL 协议。劣势复杂度高、运维成本、性能略低于纯 NoSQL。三、分库分表的生产级实战当单表数据超过千万行单库性能成为瓶颈分库分表是常见解决方案。分库分表的策略水平分表Sharding按某个字段如 user_id哈希或范围拆分到多个表。垂直分表按字段拆分将不常用字段拆分到扩展表。读写分离主库写从库读提升读性能。# 分库分表的中间件实现简化版 class ShardingRouter: def __init__(self, shards4): self.shards shards def get_shard(self, user_id): 根据 user_id 计算分片 return user_id % self.shards def get_db_and_table(self, user_id, base_tableorders): 获取分库分表的数据库和表名 shard self.get_shard(user_id) db_name forder_db_{shard} table_name f{base_table}_{shard} return db_name, table_name def execute_query(self, user_id, sql_template, params): 执行跨分片查询 db_name, table_name self.get_db_and_table(user_id) sql sql_template.format(tabletable_name) # 获取对应分片的数据库连接 conn self._get_connection(db_name) cursor conn.cursor() cursor.execute(sql, params) result cursor.fetchall() cursor.close() return result def execute_cross_shard_query(self, sql_template, params): 执行跨分片查询如统计所有订单 results [] for shard in range(self.shards): db_name forder_db_{shard} table_name forders_{shard} sql sql_template.format(tabletable_name) conn self._get_connection(db_name) cursor conn.cursor() cursor.execute(sql, params) results.extend(cursor.fetchall()) cursor.close() return results分库分表的挑战分布式事务跨分片的事务需要分布式事务如 2PC、Saga复杂度高。跨分片查询如SELECT * FROM orders WHERE create_time 2024-01-01需要查询所有分片性能差。扩容新增分片需要数据迁移影响可用性。解决方案使用中间件如 ShardingSphere、MyCAT自动处理分库分表、分布式事务、跨分片查询。NewSQL如 TiDB原生支持分布式事务和水平扩展避免分库分表的复杂度。CQRS命令查询分离写操作分库分表读操作使用物化视图或搜索引擎。四、混合持久化的架构设计现代应用通常使用多种数据库每种数据库处理其擅长的场景。这就是混合持久化。graph TB A[应用层] -- B[关系型数据库br/MySQLbr/订单/用户核心数据] A -- C[文档数据库br/MongoDBbr/日志/评论] A -- D[键值数据库br/Redisbr/缓存/会话] A -- E[搜索引擎br/Elasticsearchbr/全文搜索/日志分析] A -- F[时序数据库br/InfluxDBbr/监控指标] B --|订单数据| G[业务核心] C --|日志数据| H[运营分析] D --|缓存| I[性能加速] E --|搜索索引| J[搜索服务] F --|时序数据| K[监控系统] style A fill:#e1f5fe style B fill:#fff3e0 style D fill:#e8f5e9混合持久化的数据同步不同数据库之间需要数据同步。常见方案应用层双写应用同时写入多个数据库。优点简单缺点一致性难保证。Change Data CaptureCDC捕获数据库变更日志同步到其他数据库。优点解耦、最终一致缺点复杂度高。消息队列应用写入主库后发送消息到队列消费者更新其他数据库。优点异步、解耦缺点延迟、一致性弱。CDC 实战使用 Debezium# Debezium MySQL Connector 配置 { name: mysql-connector, config: { connector.class: io.debezium.connector.mysql.MySqlConnector, database.hostname: mysql, database.port: 3306, database.user: debezium, database.password: password, database.server.id: 184054, database.server.name: mysql, table.whitelist: order_db.orders, database.history.kafka.bootstrap.servers: kafka:9092, database.history.kafka.topic: schema-changes.mysql } }Debezium 捕获 MySQL 的 binlog将变更事件发送到 Kafka消费者消费事件并更新到 Elasticsearch 或 Redis。混合持久化的一致性保证不同数据库之间的一致性难以保证。常见策略最终一致性接受短暂不一致通过重试、补偿保证最终一致。事务性发件箱模式Transactional Outbox将数据库更新和消息发送放在同一个本地事务中保证至少一次投递。-- 事务性发件箱表 CREATE TABLE outbox ( id BIGINT PRIMARY KEY AUTO_INCREMENT, aggregate_type VARCHAR(255), aggregate_id VARCHAR(255), event_type VARCHAR(255), payload JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 应用逻辑 BEGIN TRANSACTION; -- 更新业务表 UPDATE orders SET status paid WHERE id 12345; -- 插入发件箱 INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload) VALUES (Order, 12345, OrderPaid, {order_id: 12345}); COMMIT; -- 后台任务读取 outbox 并发送到消息队列 -- 发送成功后删除 outbox 记录或标记为已发送五、数据库选型的决策框架与成本权衡数据库选型不是技术问题而是业务问题。决策框架步骤一明确业务需求数据量GB、TB 还是 PB读写比例读多写少还是写多读少一致性要求强一致性还是最终一致性查询复杂度简单 KV 还是复杂 JOIN延迟要求毫秒级还是秒级步骤二匹配数据库类型需求推荐数据库强一致性 复杂查询MySQL/PostgreSQL灵活 Schema 高吞吐写MongoDB超低延迟 简单查询Redis海量数据 高可用Cassandra全文搜索Elasticsearch关系遍历Neo4j水平扩展 强一致性TiDB/CockroachDB步骤三评估成本和风险** license 成本**商业数据库如 Oraclevs 开源数据库。运维成本是否需要专职 DBA自动化运维工具是否完善迁移成本未来迁移到其他数据库的难度和成本。生态成熟度社区活跃度、文档完整性、第三方工具支持。成本权衡的真实案例某电商公司初期使用 MySQL订单表增长到 5000 万行后性能下降。团队考虑两个方案分库分表使用 ShardingSphere成本 2 人月但需要长期维护。迁移到 TiDB成本 3 人月但长期免运维。决策选择 TiDB。原因团队规模小5 人无法承担长期分库分表的运维成本。TiDB 兼容 MySQL 协议迁移成本低。未来数据量可能继续增长TiDB 的水平扩展能力更强。数据库选型的常见陷阱盲目跟风选择热门数据库如 MongoDB但不匹配业务需求。过早优化数据量还小就引入分库分表增加不必要的复杂度。忽视运维选择前沿数据库但团队缺乏运维能力出问题时束手无策。单一数据库思维试图用一个数据库解决所有问题导致某些场景性能极差。独立开发者的实用主义建议从简单开始MySQL/PostgreSQL Redis 足以支撑早期产品。按场景引入当需要全文搜索时引入 Elasticsearch当需要图查询时引入 Neo4j。使用托管服务云厂商的数据库服务如 AWS RDS、MongoDB Atlas可以降低运维成本。建立备份和容灾机制无论选择哪个数据库备份和容灾都是必须的。深夜的架构图终于完整咖啡也凉了。数据库选型不是炫技而是解决问题。真正重要的是理解你的业务需求选择合适的工具并在性能、一致性、成本、复杂度之间找到平衡点。毕竟技术的终极目标是创造价值而不是堆砌数据库。