游戏服务器数据库设计:从玩家数据到排行榜的完整方案

引言 游戏服务器的数据库设计直接影响游戏性能和玩家体验。从高频的玩家状态更新到实时的排行榜查询,从复杂的社交关系到海量的日志数据,不同的数据类型需要不同的存储策略。本文将系统性地介绍游戏数据库的设计和优化。 数据分类 数据类型分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 """ 游戏数据分类 玩家数据: - 账号: 登录, 认证 - 角色: 档案, 属性 - 进度: 等级, 成就 游戏数据: - 配置: 物品, 任务 - 剧情: 剧情数据 - 地图: 场景数据 运行时数据: - 状态: 玩家位置, 血量 - 交易: 经济交易 - 聊天: 聊天记录 """ class GameDataClassification: """游戏数据分类""" def __init__(self): self.categories = { "玩家核心数据": { "特征": "低频读写", "重要性": "高", "存储": "关系型数据库", "示例": ["账号", "角色档案", "背包"] }, "玩家状态数据": { "特征": "高频读写", "重要性": "中", "存储": "Redis缓存", "示例": ["在线状态", "位置", "血量"] }, "游戏配置数据": { "特征": "只读", "重要性": "高", "存储": "文件或NoSQL", "示例": ["物品定义", "任务配置"] }, "社交数据": { "特征": "关系复杂", "重要性": "中", "存储": "图数据库或关系型", "示例": ["好友", "公会", "聊天"] }, "日志数据": { "特征": "海量只写", "重要性": "低", "存储": "日志系统", "示例": ["登录日志", "行为日志"] } } def storage_strategy(self): """存储策略""" strategy = { "关系型数据库": { "适用": "结构化数据, 事务", "技术": ["MySQL", "PostgreSQL"], "优化": "索引, 分区" }, "NoSQL": { "适用": "灵活schema, 大数据", "技术": ["MongoDB", "Cassandra"], "优化": "数据模型, 分片" }, "缓存": { "适用": "热数据, 计数", "技术": ["Redis", "Memcached"], "优化": "缓存策略, 过期" }, "搜索引擎": { "适用": "全文搜索", "技术": ["Elasticsearch"], "应用": "玩家搜索, 日志分析" } } return strategy 数据模型设计 玩家数据模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class PlayerDataModel: """玩家数据模型""" def __init__(self): self.tables = { "账号表": { "user_id": "PK", "username": "UK", "email": "UK", "password_hash": "", "created_at": "", "last_login": "", "索引": ["username", "email"] }, "角色表": { "character_id": "PK", "user_id": "FK", "name": "", "level": "", "class": "", "exp": "", "gold": "", "last_save": "", "索引": ["user_id", "name"] }, "背包表": { "item_id": "PK", "character_id": "FK", "item_template_id": "", "quantity": "", "slot": "", "enchant": "", "索引": ["character_id"] } } def relationship_design(self): """关系设计""" relationships = { "一对多": { "用户→角色": "一个用户多个角色", "角色→物品": "一个角色多个物品", "实现": "外键约束" }, "多对多": { "好友关系": "好友表", "公会成员": "公会成员表", "实现": "中间表" }, "一对多自引用": { "任务链": "前置任务", "技能树": "前置技能", "实现": "自引用外键" } } return relationships 分库分表 扩展策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class ShardingStrategy: """分库分表策略""" def __init__(self): self.methods = { "水平分表": { "按玩家ID": "范围或哈希", "按时间": "时间范围", "按区域": "游戏区域" }, "垂直分库": { "按业务": "用户库, 游戏库", "按读写": "主库, 从库", "按数据类型": "关系型, NoSQL" } } def sharding_approach(self): """分片方法""" approach = { "范围分片": { "方法": "按ID范围", "优势": "简单查询", "劣势": "热点问题", "示例": "0-100万, 100-200万" }, "哈希分片": { "方法": "哈希函数", "优势": "分布均匀", "劣势": "跨片查询", "示例": "id % 4" }, "地理位置": { "方法": "按玩家位置", "优势": "就近访问", "应用": "多地域部署" } } return approach def distributed_id(self): """分布式ID""" id_generation = { "雪花算法": { "结构": "时间戳+机器ID+序列", "优势": "有序, 唯一", "实现": "简单" }, "UUID": { "类型": "UUID v4", "优势": "简单", "劣势": "无序, 较长" }, "自增": { "问题": "分布式困难", "方案": "号段模式", "应用": "单机" } } return id_generation 缓存架构 多级缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class CacheArchitecture: """缓存架构""" def __init__(self): self.layers = { "本地缓存": { "位置": "应用进程内存", "容量": "小(MB级)", "速度": "最快", "技术": ["Guava", "Caffeine"] }, "分布式缓存": { "位置": "独立缓存集群", "容量": "大(GB级)", "速度": "快(网络)", "技术": ["Redis", "Memcached"] }, "CDN缓存": { "位置": "边缘节点", "内容": "静态资源", "技术": ["Cloudflare", "Akamai"] } } def redis_patterns(self): """Redis模式""" patterns = { "缓存穿透": { "问题": "查询不存在的数据", "解决": "布隆过滤器", "空值缓存": "缓存空结果" }, "缓存击穿": { "问题": "热点key过期", "解决": "互斥锁", "永不过期": "逻辑过期" }, "缓存雪崩": { "问题": "大量key同时过期", "解决": "随机过期时间", "熔断": "限流降级" } } return patterns 排行榜实现 高性能排行榜 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class LeaderboardImplementation: """排行榜实现""" def __init__(self): self.requirements = { "实时性": { "更新": "即时更新", "查询": "快速查询", "排名": "实时排名" }, "性能": { "并发": "高并发读写", "延迟": "低延迟响应", "规模": "百万级玩家" } } def redis_sorted_set(self): """Redis Sorted Set实现""" implementation = { "数据结构": { "类型": "Sorted Set", "成员": "玩家ID", "分数": "分数/积分" }, "操作": { "ZADD": "添加或更新分数", "ZINCRBY": "增加分数", "ZRANGE": "获取排名", "ZREVRANK": "获取排名" }, "优化": { "分片": "按范围分片", "过期": "定期清理", "快照": "定期快照" } } return implementation def advanced_features(self): """高级特性""" features = { "多维度": { "全服": "全服排行", "好友": "好友排行", "公会": "公会排行" }, "时效性": { "实时": "实时更新", "每日": "每日重置", "每周": "每周排行" }, "奖励": { "定时": "根据排名发奖", "通知": "结果通知", "记录": "历史记录" } } return features 数据一致性 事务处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class DataConsistency: """数据一致性""" def __init__(self): self.scenarios = { "交易": { "需求": "强一致性", "方法": "数据库事务", "隔离": "Serializable隔离" }, "排行榜": { "需求": "最终一致性", "方法": "Redis + 异步持久化", "优化": "定期同步" }, "社交": { "需求": "最终一致性", "方法": "消息队列", "重试": "失败重试" } } def transaction_patterns(self): """事务模式""" patterns = { "本地事务": { "描述": "单数据库事务", "实现": "BEGIN/COMMIT", "应用": "单库操作" }, "分布式事务": { "2PC": "两阶段提交", "Saga": "补偿事务", "TCC": "Try-Confirm-Cancel" }, "最终一致性": { "事件": "事件驱动", "重试": "指数退避", "死信队列": "失败处理" } } return patterns 性能优化 数据库优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class DatabaseOptimization: """数据库优化""" def __init__(self): self.techniques = { "索引": { "主键索引": "自动创建", "唯一索引": "唯一约束", "复合索引": "多列索引", "覆盖索引": "包含查询列" }, "查询": { "优化": "EXPLAIN分析", "避免": "SELECT *", "限制": "LIMIT分页", "缓存": "查询结果缓存" }, "表设计": { "范式": "适当范式化", "反范式": "查询优化", "分区": "表分区", "分表": "水平拆分" } } def monitoring(self): """监控指标""" metrics = { "性能": { "QPS": "每秒查询数", "延迟": "查询响应时间", "慢查询": "慢查询日志" }, "资源": { "连接": "连接池使用", "CPU": "数据库CPU", "内存": "缓冲池使用", "磁盘": "IO使用" }, "复制": { "延迟": "主从延迟", "状态": "复制状态", "可用": "从库可用性" } } return metrics 最佳实践 设计原则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class BestPractices: """最佳实践""" def __init__(self): self.principles = { "设计": { "简单": "开始简单", "范式": "适当范式化", "索引": "合理索引", "分区": "提前规划" }, "开发": { "迁移": "版本迁移", "测试": "充分测试", "备份": "定期备份", "监控": "持续监控" }, "运维": { "高可用": "主从复制", "读写分离": "分离读写", "负载均衡": "均衡负载", "故障转移": "自动转移" } } def common_mistakes(self): """常见错误""" mistakes = { "过度索引": { "问题": "索引太多", "影响": "写入性能", "解决": "只索引需要的列" }, "N+1查询": { "问题": "循环查询", "解决": "JOIN或批量查询", "示例": "批量加载玩家数据" }, "大事务": { "问题": "事务太长", "影响": "锁定, 死锁", "解决": "拆分事务" } } return mistakes 总结 游戏服务器数据库设计需要在数据一致性、性能和扩展性之间取得平衡。通过合理的数据分类、存储策略选择和持续的性能优化,可以构建出满足游戏需求的高性能数据存储方案。 ...

数据库分库分表架构设计指南:从单表到百亿级数据的演进之路

引言:为什么需要分库分表? MySQL 单表性能瓶颈: 数据量:单表超过 1000 万行,查询性能显著下降 索引大小:索引树高度增加,磁盘 I/O 增多 锁竞争:高并发下锁等待严重 备份恢复:单表过大,备份耗时长 分库分表是突破单机数据库瓶颈的有效手段,但它也带来了复杂的路由逻辑和运维成本。本文将系统地介绍分库分表的实践方案。 一、垂直拆分 vs 水平拆分 1.1 垂直拆分(按业务维度) 单体数据库: ┌─────────────────────────────────────────┐ │ single_database │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ user │ │ order │ │ product │ │ │ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────┘ ↓ 垂直拆分 ┌─────────┐ ┌─────────┐ ┌─────────┐ │user_db │ │order_db │ │product_db│ │ │ │ │ │ │ │ user │ │ order │ │ product │ │ profile │ │ payment │ │ inventory│ └─────────┘ └─────────┘ └─────────┘ 优点: ...

数据库优化与分布式存储:构建高性能数据架构

引言 数据是现代应用的核心资产。随着数据量的爆炸式增长,数据库性能和可扩展性成为系统设计的关键挑战。本文将深入探讨数据库优化技巧和分布式存储架构,帮助读者构建高性能的数据架构。 一、数据库性能优化 1.1 索引优化策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 -- ========== 索引基础 ========== -- 1. B-Tree索引(最常用) -- 适合:等值查询、范围查询、排序 CREATE INDEX idx_user_email ON users(email); CREATE INDEX idx_order_created ON orders(created_at); -- 2. 复合索引 -- 注意:最左前缀原则 CREATE INDEX idx_user_status_created ON users(status, created_at); -- 有效的查询(能使用索引) SELECT * FROM users WHERE status = 1 AND created_at > '2024-01-01'; SELECT * FROM users WHERE status = 1; -- 无效的查询(不能使用索引) SELECT * FROM users WHERE created_at > '2024-01-01'; -- 3. 覆盖索引 -- 包含查询所需的所有字段,避免回表 CREATE INDEX idx_user_cover ON users(status, created_at, id, name); -- 4. 唯一索引 CREATE UNIQUE INDEX idx_user_username ON users(username); -- ========== 索引设计原则 ========== /* 1. 选择性高的字段适合建索引 - 高选择性:唯一值多(如用户ID、邮箱) - 低选择性:重复值多(如性别、状态) 2. WHERE、JOIN、ORDER BY子句的字段 3. 小字段优先 - 整数 > 日期 > 短字符串 > 长字符串 4. 联合索引的顺序 - 最常查询的字段放前面 - 范围查询字段放后面 5. 避免过多索引 - 降低写入性能 - 占用存储空间 */ -- ========== 索引优化案例 ========== -- 问题:查询慢 -- 耗时:2.5秒 SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND o.created_at > '2024-01-01' ORDER BY o.created_at DESC LIMIT 20; -- 优化1:添加合适索引 CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC); CREATE INDEX idx_orders_user_id ON orders(user_id); -- 耗时:0.3秒 -- 优化2:只查询需要的字段 SELECT o.id, o.order_no, o.total_amount, u.username FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND o.created_at > '2024-01-01' ORDER BY o.created_at DESC LIMIT 20; -- 耗时:0.1秒 -- 优化3:使用覆盖索引 CREATE INDEX idx_orders_cover ON orders( status, created_at DESC, id, order_no, total_amount, user_id ); -- 耗时:0.05秒 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 # ========== 索引分析与维护 ========== import pymysql from typing import List, Dict, Tuple class IndexAnalyzer: """索引分析器""" def __init__(self, connection_config: dict): self.conn = pymysql.connect(**connection_config) def analyze_table_indexes(self, table_name: str) -> List[Dict]: """分析表的索引使用情况""" with self.conn.cursor() as cursor: # 查询索引信息 sql = """ SHOW INDEX FROM %s """ % table_name cursor.execute(sql) indexes = cursor.fetchall() # 查询索引使用统计 sql = """ SELECT table_name, index_name, cardinality, column_name, seq_in_index FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = %s ORDER BY index_name, seq_in_index """ cursor.execute(sql, (table_name,)) stats = cursor.fetchall() return { 'indexes': indexes, 'statistics': stats } def find_unused_indexes(self) -> List[Dict]: """查找未使用的索引""" with self.conn.cursor() as cursor: # MySQL 5.7+ 使用performance_schema sql = """ SELECT object_schema AS table_schema, object_name AS table_name, index_name FROM performance_schema.table_io_waits_summary_by_index_usage WHERE index_name IS NOT NULL AND count_star = 0 AND index_name != 'PRIMARY' ORDER BY object_schema, object_name """ cursor.execute(sql) return cursor.fetchall() def find_duplicate_indexes(self, table_name: str) -> List[Tuple]: """查找冗余索引""" with self.conn.cursor() as cursor: # 查询索引及其列 sql = """ SELECT index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) as columns FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = %s GROUP BY index_name HAVING COUNT(*) > 1 """ cursor.execute(sql, (table_name,)) indexes = cursor.fetchall() # 查找冗余索引 duplicates = [] for i, idx1 in enumerate(indexes): for idx2 in indexes[i+1:]: # 检查是否包含关系 if idx2[1].startswith(idx1[1]): duplicates.append((idx1[0], idx2[0])) return duplicates def suggest_indexes(self, table_name: str) -> List[Dict]: """推荐索引""" with self.conn.cursor() as cursor: # 分析慢查询日志 sql = """ SELECT sql_text, count(*) as frequency FROM mysql.slow_log WHERE sql_text LIKE %s GROUP BY sql_text ORDER BY frequency DESC LIMIT 10 """ cursor.execute(sql, (f'%{table_name}%',)) slow_queries = cursor.fetchall() suggestions = [] for query, freq in slow_queries: # 提取WHERE条件字段 # 这里简化处理,实际需要解析SQL # ... suggestions.append({ 'query': query, 'frequency': freq, 'suggested_index': '建议基于WHERE子句创建索引' }) return suggestions 1.2 查询优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 -- ========== SQL查询优化 ========== -- 1. 避免SELECT * -- 不推荐 SELECT * FROM users WHERE id = 1; -- 推荐 SELECT id, username, email FROM users WHERE id = 1; -- 2. 使用LIMIT限制结果集 SELECT * FROM orders WHERE status = 'pending' LIMIT 100; -- 3. 优化JOIN -- 小表驱动大表 SELECT * FROM small_table s JOIN large_table l ON s.id = l.small_id; -- 4. 子查询优化 -- 不推荐 SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000); -- 推荐 SELECT DISTINCT u.* FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.amount > 1000; -- 5. EXISTS vs IN -- 小表用IN,大表用EXISTS -- 小表 SELECT * FROM users WHERE id IN (1, 2, 3); -- 大表 SELECT * FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'pending' ); -- 6. 避免在WHERE子句中使用函数 -- 不推荐 SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01'; -- 推荐 SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'; -- 7. 使用UNION ALL代替UNION -- 不推荐(去重开销大) SELECT user_id FROM orders_2024 UNION SELECT user_id FROM orders_2023; -- 推荐 SELECT user_id FROM orders_2024 UNION ALL SELECT user_id FROM orders_2023; -- 8. 批量操作 -- 不推荐(多次单条插入) INSERT INTO logs (message) VALUES ('log1'); INSERT INTO logs (message) VALUES ('log2'); INSERT INTO logs (message) VALUES ('log3'); -- 推荐 INSERT INTO logs (message) VALUES ('log1'), ('log2'), ('log3'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 # ========== 查询优化器 ========== import re from typing import List, Dict, Tuple class SQLQueryOptimizer: """SQL查询优化器""" def __init__(self): self.rules = { 'select_star': self._check_select_star, 'missing_where': self._check_missing_where, 'function_in_where': self._check_function_in_where, 'subquery': self._check_subquery, 'nplus1': self._check_nplus1 } def optimize(self, sql: str) -> Dict: """优化SQL""" issues = [] suggestions = [] for rule_name, rule_func in self.rules.items(): result = rule_func(sql) if result: issues.append(result['issue']) suggestions.append(result['suggestion']) return { 'original_sql': sql, 'issues': issues, 'suggestions': suggestions } def _check_select_star(self, sql: str) -> Dict: """检查SELECT ***""" if re.search(r'SELECT\s+\*\s+FROM', sql, re.IGNORECASE): return { 'issue': '使用SELECT *', 'suggestion': '只查询需要的列,减少数据传输' } return None def _check_missing_where(self, sql: str) -> Dict: """检查缺少WHERE条件""" if re.search(r'(DELETE|UPDATE)\s+\w+\s+(?!WHERE)', sql, re.IGNORECASE): return { 'issue': 'DELETE/UPDATE缺少WHERE条件', 'suggestion': '添加WHERE条件,避免全表操作' } return None def _check_function_in_where(self, sql: str) -> Dict: """检查WHERE子句中使用函数""" if re.search( r'WHERE\s+.*(?:DATE|YEAR|MONTH|DAY)\(', sql, re.IGNORECASE ): return { 'issue': 'WHERE子句中使用函数', 'suggestion': '将函数作用到比较值上,而非字段' } return None def _check_subquery(self, sql: str) -> Dict: """检查子查询""" if 'IN (SELECT' in sql.upper(): return { 'issue': '使用IN子查询', 'suggestion': '考虑改用JOIN或EXISTS' } return None def _check_nplus1(self, sql: str) -> Dict: """检查N+1查询""" # 这里简化处理,实际需要分析执行模式 return None class QueryExecutionAnalyzer: """查询执行分析器""" def __init__(self, connection): self.conn = connection def explain_query(self, sql: str) -> Dict: """分析查询执行计划""" with self.conn.cursor() as cursor: # 执行EXPLAIN explain_sql = f"EXPLAIN {sql}" cursor.execute(explain_sql) result = cursor.fetchall() analysis = { 'type': [], 'table': [], 'possible_keys': [], 'key': [], 'rows': [], 'filtered': [], 'extra': [] } for row in result: analysis['type'].append(row['type']) analysis['table'].append(row['table']) analysis['possible_keys'].append(row['possible_keys']) analysis['key'].append(row['key']) analysis['rows'].append(row['rows']) analysis['filtered'].append(row['filtered']) analysis['extra'].append(row['Extra']) # 分析结果 suggestions = [] # 检查是否全表扫描 if 'ALL' in analysis['type']: suggestions.append( '警告:存在全表扫描,考虑添加索引' ) # 检查是否使用了索引 if None in analysis['key']: suggestions.append( '部分查询没有使用索引,检查索引设计' ) # 检查扫描行数 if analysis['rows'] and sum(analysis['rows']) > 10000: suggestions.append( '扫描行数较多,考虑优化查询或索引' ) return { 'explain_plan': result, 'suggestions': suggestions } def profile_query(self, sql: str) -> Dict: """分析查询性能""" with self.conn.cursor() as cursor: # 开启profiling cursor.execute("SET profiling = 1") # 执行查询 cursor.execute(sql) # 获取profiling结果 cursor.execute("SHOW PROFILE") profile = cursor.fetchall() # 获取查询统计 cursor.execute("SHOW PROFILE FOR QUERY 1") stats = cursor.fetchall() return { 'profile': profile, 'statistics': stats } 二、分库分表策略 2.1 水平分表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 # ========== 分表策略 ========== class ShardingStrategy: """分片策略""" def __init__(self, shard_count: int): self.shard_count = shard_count def hash_sharding(self, key: str) -> int: """哈希分片""" hash_value = hash(key) return hash_value % self.shard_count def range_sharding(self, key: int) -> int: """范围分片""" # 假设key是自增ID shard_size = 1000000 # 每个分片100万条 return key // shard_size def modulo_sharding(self, key: int) -> int: """取模分片""" return key % self.shard_count def consistent_hash_sharding(self, key: str) -> int: """一致性哈希分片""" import hashlib hash_value = int(hashlib.md5(key.encode()).hexdigest(), 16) return hash_value % self.shard_count class ShardingManager: """分片管理器""" def __init__(self, shard_configs: List[dict]): self.shards = {} for config in shard_configs: shard_id = config['id'] self.shards[shard_id] = { 'connection': self._create_connection(config), 'config': config } self.strategy = ShardingStrategy(len(shard_configs)) def _create_connection(self, config: dict): """创建数据库连接""" import pymysql return pymysql.connect( host=config['host'], port=config['port'], user=config['user'], password=config['password'], database=config['database'] ) def get_shard(self, shard_key: str, sharding_type: str = 'hash'): """获取分片连接""" if sharding_type == 'hash': shard_id = self.strategy.hash_sharding(shard_key) elif sharding_type == 'consistent': shard_id = self.strategy.consistent_hash_sharding(shard_key) elif sharding_type == 'modulo': shard_id = self.strategy.modulo_sharding(int(shard_key)) else: raise ValueError(f"Unknown sharding type: {sharding_type}") return self.shards[shard_id]['connection'] def execute_on_shard(self, shard_key: str, sql: str, params=None): """在指定分片执行SQL""" conn = self.get_shard(shard_key) with conn.cursor() as cursor: cursor.execute(sql, params or ()) return cursor.fetchall() def query_all_shards(self, sql: str, params=None): """查询所有分片""" results = [] for shard_id, shard in self.shards.items(): conn = shard['connection'] with conn.cursor() as cursor: cursor.execute(sql, params or ()) shard_results = cursor.fetchall() # 添加分片标识 for row in shard_results: if isinstance(row, dict): row['_shard_id'] = shard_id results.extend(shard_results) return results def broadcast_write(self, sql: str, params=None): """广播写入所有分片""" results = [] for shard_id, shard in self.shards.items(): conn = shard['connection'] with conn.cursor() as cursor: cursor.execute(sql, params or ()) conn.commit() results.append({ 'shard_id': shard_id, 'affected_rows': cursor.rowcount }) return results # ========== 分表路由 ========== class TableRouter: """表路由""" def __init__(self, sharding_manager: ShardingManager): self.manager = sharding_manager self.table_rules = {} def add_rule(self, table_name: str, sharding_key: str, sharding_type: str): """添加分表规则""" self.table_rules[table_name] = { 'sharding_key': sharding_key, 'sharding_type': sharding_type } def get_table_name(self, original_table: str, shard_key: str) -> str: """获取实际表名""" rule = self.table_rules.get(original_table) if not rule: return original_table # 计算分片ID if rule['sharding_type'] == 'hash': shard_id = hash(shard_key) % self.manager.strategy.shard_count elif rule['sharding_type'] == 'modulo': shard_id = int(shard_key) % self.manager.strategy.shard_count else: shard_id = 0 return f"{original_table}_{shard_id:04d}" def execute_query(self, table_name: str, query_template: str, **kwargs): """执行分表查询""" # 获取分片键值 rule = self.table_rules.get(table_name) if not rule: # 不分表,直接执行 return self.manager.execute_on_shard('0', query_template, kwargs) shard_key_value = kwargs.get(rule['sharding_key']) if not shard_key_value: raise ValueError(f"Missing sharding key: {rule['sharding_key']}") # 获取实际表名 actual_table = self.get_table_name(table_name, str(shard_key_value)) # 替换表名 actual_query = query_template.replace(f'FROM {table_name}', f'FROM {actual_table}') return self.manager.execute_on_shard(str(shard_key_value), actual_query, kwargs) # 使用示例 shard_configs = [ { 'id': 0, 'host': 'db0.example.com', 'port': 3306, 'user': 'root', 'password': 'password', 'database': 'app_db_0' }, { 'id': 1, 'host': 'db1.example.com', 'port': 3306, 'user': 'root', 'password': 'password', 'database': 'app_db_1' }, { 'id': 2, 'host': 'db2.example.com', 'port': 3306, 'user': 'root', 'password': 'password', 'database': 'app_db_2' }, { 'id': 3, 'host': 'db3.example.com', 'port': 3306, 'user': 'root', 'password': 'password', 'database': 'app_db_3' } ] sharding_manager = ShardingManager(shard_configs) table_router = TableRouter(sharding_manager) # 配置orders表按user_id分表 table_router.add_rule('orders', 'user_id', 'modulo') # 插入订单(自动路由到正确的分片) table_router.execute_query( 'orders', "INSERT INTO orders (user_id, order_no, total_amount) VALUES (:user_id, :order_no, :total_amount)", user_id=12345, order_no='ORD2024010112345', total_amount=9999.99 ) # 查询订单(自动路由到正确的分片) results = table_router.execute_query( 'orders', "SELECT * FROM orders WHERE user_id = :user_id", user_id=12345 ) 2.2 垂直分库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 # ========== 垂直分库策略 ========== class VerticalSharding: """垂直分库""" def __init__(self): self.databases = { 'user_db': None, # 用户相关表 'order_db': None, # 订单相关表 'product_db': None, # 商品相关表 'log_db': None # 日志相关表 } def route_by_module(self, table_name: str): """按模块路由""" module_mapping = { # 用户模块 'users': 'user_db', 'user_profiles': 'user_db', 'user_addresses': 'user_db', 'user_preferences': 'user_db', # 订单模块 'orders': 'order_db', 'order_items': 'order_db', 'order_payments': 'order_db', 'order_shippings': 'order_db', # 商品模块 'products': 'product_db', 'categories': 'product_db', 'product_skus': 'product_db', 'inventory': 'product_db', # 日志模块 'operation_logs': 'log_db', 'access_logs': 'log_db', 'error_logs': 'log_db' } return self.databases.get(module_mapping.get(table_name)) def cross_database_query(self, queries: List[dict]): """跨库查询""" results = {} for query_info in queries: db_name = query_info['database'] sql = query_info['sql'] params = query_info.get('params', {}) conn = self.databases[db_name] with conn.cursor() as cursor: cursor.execute(sql, params) results[db_name] = cursor.fetchall() # 在应用层合并结果 return self._merge_results(results) def _merge_results(self, results: dict) -> List[dict]: """合并跨库查询结果""" # 根据业务逻辑合并结果 # 这里是简化示例 merged = [] # 从user_db获取用户信息 users = results.get('user_db', []) # 从order_db获取订单信息 orders = results.get('order_db', []) # 合并数据 user_dict = {user['id']: user for user in users} for order in orders: user_id = order['user_id'] if user_id in user_dict: merged.append({ **user_dict[user_id], 'order': order }) return merged # ========== 分布式事务(Saga模式) ========== class DistributedTransaction: """分布式事务管理器""" def __init__(self): self.participants = [] self.compensations = [] def add_participant(self, database: str, operation: str, compensation: str): """添加事务参与者""" self.participants.append({ 'database': database, 'operation': operation }) self.compensations.append({ 'database': database, 'operation': compensation }) async def execute(self) -> bool: """执行分布式事务""" executed = [] try: # 顺序执行各分库操作 for participant in self.participants: conn = self.databases[participant['database']] with conn.cursor() as cursor: cursor.execute(participant['operation']) conn.commit() executed.append(participant) return True except Exception as e: # 执行补偿操作 for i in range(len(executed) - 1, -1, -1): compensation = self.compensations[i] try: conn = self.databases[compensation['database']] with conn.cursor() as cursor: cursor.execute(compensation['operation']) conn.commit() except Exception as ce: # 补偿失败,记录日志 print(f"Compensation failed: {ce}") return False 三、分布式数据库 3.1 NewSQL数据库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 # ========== TiDB集成示例 ========== import MySQLdb class TiDBClient: """TiDB客户端""" def __init__(self, host: str, port: int, user: str, password: str, database: str): self.conn = MySQLdb.connect( host=host, port=port, user=user, password=password, database=database, charset='utf8mb4' ) def insert_batch(self, table: str, records: List[dict]) -> int: """批量插入""" if not records: return 0 # 构建批量插入SQL columns = list(records[0].keys()) placeholders = ', '.join(['%s'] * len(columns)) sql = f""" INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders}) """ values = [ tuple(record[col] for col in columns) for record in records ] with self.conn.cursor() as cursor: cursor.executemany(sql, values) self.conn.commit() return cursor.rowcount def select_with_pagination( self, table: str, where: str = None, order_by: str = None, limit: int = 100, offset: int = 0 ) -> List[dict]: """分页查询""" sql = f"SELECT * FROM {table}" if where: sql += f" WHERE {where}" if order_by: sql += f" ORDER BY {order_by}" sql += f" LIMIT {limit} OFFSET {offset}" with self.conn.cursor(MySQLdb.cursors.DictCursor) as cursor: cursor.execute(sql) return cursor.fetchall() def get_transaction_info(self) -> dict: """获取事务信息""" with self.conn.cursor() as cursor: # 查询当前事务信息 cursor.execute("SELECT @@txn_version as version") version = cursor.fetchone() cursor.execute("SELECT @@autocommit as autocommit") autocommit = cursor.fetchone() return { 'version': version[0] if version else None, 'autocommit': autocommit[0] if autocommit else None } # ========== 分布式事务使用 ========== class DistributedOrderService: """分布式订单服务""" def __init__(self, tidb_client: TiDBClient): self.tidb = tidb_client async def create_order(self, order_data: dict, items: List[dict]) -> str: """创建订单(分布式事务)""" try: # 开启事务 with self.tidb.conn as cursor: # 1. 创建订单 order_sql = """ INSERT INTO orders (user_id, order_no, total_amount, status) VALUES (%s, %s, %s, %s) """ cursor.execute(order_sql, ( order_data['user_id'], order_data['order_no'], order_data['total_amount'], 'pending' )) order_id = cursor.lastrowid # 2. 创建订单明细 for item in items: item_sql = """ INSERT INTO order_items ( order_id, product_id, quantity, price ) VALUES (%s, %s, %s, %s) """ cursor.execute(item_sql, ( order_id, item['product_id'], item['quantity'], item['price'] )) # 3. 扣减库存 for item in items: inventory_sql = """ UPDATE inventory SET stock = stock - %s WHERE product_id = %s AND stock >= %s """ affected = cursor.execute(inventory_sql, ( item['quantity'], item['product_id'], item['quantity'] )) if affected == 0: # 库存不足,回滚事务 raise Exception(f"Insufficient stock for product {item['product_id']}") # 4. 创建支付记录 payment_sql = """ INSERT INTO payments ( order_id, amount, status, payment_method ) VALUES (%s, %s, %s, %s) """ cursor.execute(payment_sql, ( order_id, order_data['total_amount'], 'pending', order_data['payment_method'] )) # 提交事务 self.tidb.conn.commit() return order_id except Exception as e: # 回滚事务 self.tidb.conn.rollback() raise e 3.2 分布式缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 # ========== Redis集群 ========== import redis from rediscluster import RedisCluster class RedisClusterClient: """Redis集群客户端""" def __init__(self, startup_nodes: List[dict]): """ startup_nodes: [ {'host': 'redis1.example.com', 'port': 7000}, {'host': 'redis2.example.com', 'port': 7001}, {'host': 'redis3.example.com', 'port': 7002} ] """ self.client = RedisCluster( startup_nodes=startup_nodes, decode_responses=True, skip_full_coverage_check=True, max_connections=32 ) def set(self, key: str, value: str, expire: int = None): """设置键值""" return self.client.set(key, value, ex=expire) def get(self, key: str) -> str: """获取值""" return self.client.get(key) def mget(self, keys: List[str]) -> List[str]: """批量获取""" return self.client.mget(keys) def delete(self, *keys: str): """删除键""" return self.client.delete(*keys) def exists(self, *keys: str) -> int: """检查键是否存在""" return self.client.exists(*keys) def expire(self, key: str, seconds: int): """设置过期时间""" return self.client.expire(key, seconds) def incr(self, key: str, amount: int = 1) -> int: """递增""" return self.client.incrby(key, amount) def decr(self, key: str, amount: int = 1) -> int: """递减""" return self.client.decrby(key, amount) def hset(self, name: str, key: str, value: str): """哈希表设置""" return self.client.hset(name, key, value) def hget(self, name: str, key: str) -> str: """哈希表获取""" return self.client.hget(name, key) def hgetall(self, name: str) -> dict: """获取整个哈希表""" return self.client.hgetall(name) # ========== 缓存策略 ========== class CacheStrategy: """缓存策略""" def __init__(self, redis_client: RedisClusterClient): self.redis = redis_client def cache_aside(self, key: str, load_func, expire: int = 3600): """Cache-Aside模式""" # 先查缓存 value = self.redis.get(key) if value is not None: return value # 缓存未命中,加载数据 value = load_func() # 写入缓存 self.redis.set(key, value, expire) return value def invalidate(self, *keys: str): """使缓存失效""" self.redis.delete(*keys) def warm_up(self, data: dict, expire: int = 3600): """缓存预热""" pipe = self.redis.client.pipeline() for key, value in data.items(): pipe.set(key, value, expire) pipe.execute() def update(self, key: str, value: str, expire: int = 3600): """更新缓存""" self.redis.set(key, value, expire) # ========== 分布式锁 ========== class DistributedLock: """分布式锁""" def __init__(self, redis_client: RedisClusterClient): self.redis = redis_client def acquire( self, lock_name: str, acquire_timeout: int = 10, lock_timeout: int = 30 ) -> bool: """获取锁""" import time lock_key = f"lock:{lock_name}" lock_value = f"{time.time()}" end_time = time.time() + acquire_timeout while time.time() < end_time: # 尝试获取锁 if self.redis.client.set( lock_key, lock_value, nx=True, ex=lock_timeout ): return True time.sleep(0.001) return False def release(self, lock_name: str): """释放锁""" lock_key = f"lock:{lock_name}" # 使用Lua脚本确保只释放自己的锁 lua_script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ self.redis.client.eval( lua_script, 1, lock_key, self.redis.get(lock_key) ) # ========== 限流器 ========== class RateLimiter: """分布式限流器""" def __init__(self, redis_client: RedisClusterClient): self.redis = redis_client def is_allowed( self, key: str, limit: int, window: int ) -> bool: """ 滑动窗口限流 key: 限流键(如用户ID、IP等) limit: 时间窗口内最大请求数 window: 时间窗口(秒) """ import time now = time.time() window_start = now - window pipe = self.redis.client.pipeline() # 移除时间窗口外的记录 pipe.zremrangebyscore(key, 0, window_start) # 获取当前计数 pipe.zcard(key) # 添加当前请求 pipe.zadd(key, {str(now): now}) # 设置过期时间 pipe.expire(key, window + 1) results = pipe.execute() current_count = results[1] return current_count < limit 四、数据库监控与运维 4.1 性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 # ========== 数据库监控 ========== class DatabaseMonitor: """数据库监控""" def __init__(self, connection): self.conn = connection def get_connection_stats(self) -> dict: """获取连接统计""" with self.conn.cursor() as cursor: cursor.execute(""" SHOW STATUS LIKE 'Threads%' """) stats = cursor.fetchall() return { 'threads_connected': next( (s[1] for s in stats if s[0] == 'Threads_connected'), 0 ), 'threads_running': next( (s[1] for s in stats if s[0] == 'Threads_running'), 0 ) } def get_query_stats(self) -> dict: """获取查询统计""" with self.conn.cursor() as cursor: # 慢查询统计 cursor.execute(""" SELECT COUNT(*) as slow_query_count, AVG(query_time) as avg_query_time, MAX(query_time) as max_query_time FROM mysql.slow_log WHERE start_time > DATE_SUB(NOW(), INTERVAL 1 HOUR) """) slow_stats = cursor.fetchone() # QPS/TPS统计 cursor.execute(""" SHOW STATUS LIKE 'Questions' """) questions = cursor.fetchone() cursor.execute(""" SHOW STATUS LIKE 'Uptime' """) uptime = cursor.fetchone() qps = questions[1] / uptime[1] if uptime[1] > 0 else 0 return { 'slow_query_count': slow_stats[0], 'avg_query_time': float(slow_stats[1]) if slow_stats[1] else 0, 'max_query_time': float(slow_stats[2]) if slow_stats[2] else 0, 'qps': round(qps, 2) } def get_replication_lag(self) -> int: """获取主从延迟""" with self.conn.cursor() as cursor: cursor.execute("SHOW SLAVE STATUS") status = cursor.fetchone() if status: return status['Seconds_Behind_Master'] return 0 def get_innodb_stats(self) -> dict: """获取InnoDB统计""" with self.conn.cursor() as cursor: cursor.execute(""" SHOW STATUS LIKE 'Innodb_%' """) stats = cursor.fetchall() return { 'row_lock_waits': next( (s[1] for s in stats if s[0] == 'Innodb_row_lock_current_waits'), 0 ), 'deadlocks': next( (s[1] for s in stats if s[0] == 'Innodb_deadlocks'), 0 ), 'buffer_pool_hit_rate': self._calculate_hit_rate(stats) } def _calculate_hit_rate(self, stats: list) -> float: """计算缓冲池命中率""" reads = next( (s[1] for s in stats if s[0] == 'Innodb_buffer_pool_reads'), 0 ) read_requests = next( (s[1] for s in stats if s[0] == 'Innodb_buffer_pool_read_requests'), 1 ) if read_requests == 0: return 100.0 hit_rate = (1 - reads / read_requests) * 100 return round(hit_rate, 2) # ========== 慢查询分析 ========== class SlowQueryAnalyzer: """慢查询分析器""" def __init__(self, connection): self.conn = connection def get_slow_queries(self, limit: int = 100) -> List[dict]: """获取慢查询""" with self.conn.cursor() as cursor: sql = """ SELECT query_time, lock_time, rows_sent, rows_examined, sql_text FROM mysql.slow_log ORDER BY query_time DESC LIMIT %s """ cursor.execute(sql, (limit,)) return cursor.fetchall() def analyze_slow_query(self, query: str) -> dict: """分析慢查询""" analyzer = SQLQueryOptimizer() return analyzer.optimize(query) def suggest_indexes(self, query: str) -> List[str]: """推荐索引""" # 提取WHERE、JOIN、ORDER BY子句中的字段 # 这里简化处理 import re # 提取表名 tables = re.findall(r'FROM\s+(\w+)', query, re.IGNORECASE) tables += re.findall(r'JOIN\s+(\w+)', query, re.IGNORECASE) # 提取WHERE条件字段 where_fields = re.findall( r'WHERE\s+(\w+)\.\w+\s*=', query, re.IGNORECASE ) suggestions = [] for table in set(tables): for field in set(where_fields): suggestions.append( f"CREATE INDEX idx_{table}_{field} ON {table}({field})" ) return suggestions 4.2 备份与恢复 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 # ========== 数据库备份 ========== import subprocess import os from datetime import datetime from typing import List class DatabaseBackup: """数据库备份""" def __init__( self, host: str, user: str, password: str, backup_dir: str = '/backup' ): self.host = host self.user = user self.password = password self.backup_dir = backup_dir os.makedirs(backup_dir, exist_ok=True) def backup_database( self, database: str, backup_type: str = 'full' ) -> str: """备份数据库""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') backup_file = os.path.join( self.backup_dir, f"{database}_{backup_type}_{timestamp}.sql" ) # 使用mysqldump备份 command = [ 'mysqldump', f'--host={self.host}', f'--user={self.user}', f'--password={self.password}', '--single-transaction', '--routines', '--triggers', '--events', '--quick', database ] with open(backup_file, 'w') as f: subprocess.run( command, stdout=f, stderr=subprocess.PIPE, check=True ) # 压缩备份文件 self._compress_file(backup_file) return f"{backup_file}.gz" def backup_all_databases(self) -> str: """备份所有数据库""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') backup_file = os.path.join( self.backup_dir, f"all_databases_{timestamp}.sql" ) command = [ 'mysqldump', f'--host={self.host}', f'--user={self.user}', f'--password={self.password}', '--all-databases', '--single-transaction', '--routines', '--triggers', '--events' ] with open(backup_file, 'w') as f: subprocess.run( command, stdout=f, stderr=subprocess.PIPE, check=True ) self._compress_file(backup_file) return f"{backup_file}.gz" def _compress_file(self, filepath: str): """压缩文件""" import gzip with open(filepath, 'rb') as f_in: with gzip.open(f"{filepath}.gz", 'wb') as f_out: f_out.writelines(f_in) os.remove(filepath) def restore_database(self, backup_file: str, database: str = None): """恢复数据库""" # 解压备份文件 if backup_file.endswith('.gz'): import gzip temp_file = backup_file[:-3] with gzip.open(backup_file, 'rb') as f_in: with open(temp_file, 'wb') as f_out: f_out.writelines(f_in) backup_file = temp_file # 恢复数据库 command = [ 'mysql', f'--host={self.host}', f'--user={self.user}', f'--password={self.password}' ] if database: command.append(database) with open(backup_file, 'r') as f: subprocess.run( command, stdin=f, stderr=subprocess.PIPE, check=True ) # 清理临时文件 if backup_file.endswith('.sql'): os.remove(backup_file) def list_backups(self) -> List[dict]: """列出备份文件""" backups = [] for filename in os.listdir(self.backup_dir): if filename.endswith('.sql.gz'): filepath = os.path.join(self.backup_dir, filename) stat = os.stat(filepath) backups.append({ 'filename': filename, 'size': stat.st_size, 'created_at': datetime.fromtimestamp(stat.st_ctime) }) return sorted(backups, key=lambda x: x['created_at'], reverse=True) def cleanup_old_backups(self, keep_days: int = 7): """清理旧备份""" from datetime import timedelta cutoff_time = datetime.now() - timedelta(days=keep_days) for filename in os.listdir(self.backup_dir): filepath = os.path.join(self.backup_dir, filename) stat = os.stat(filepath) if datetime.fromtimestamp(stat.st_ctime) < cutoff_time: os.remove(filepath) print(f"Removed old backup: {filename}") 总结 构建高性能的数据架构需要: ...