微爱帮监狱寄信写信小程序数据库优化技术文档

作者 | 微爱帮CTO
日期 | 2025年12月

一、数据库架构概述

1.1 设计原则

复制代码
安全第一 → 性能稳定 → 易于维护 → 成本合理

1.2 数据库架构图

复制代码
┌─────────────────────────────────────────────┐
│               应用服务器集群                 │
│   PHP + Nginx (8节点,负载均衡)             │
└───────────────┬───────────────┬─────────────┘
                │               │
        ┌───────▼───────┐ ┌─────▼─────┐
        │   主数据库     │ │ 从数据库  │
        │   MySQL 8.0   │ │ (读写分离) │
        │   监狱核心数据 │ │  家属数据  │
        └───────┬───────┘ └─────┬─────┘
                │               │
        ┌───────▼───────┐ ┌─────▼─────┐
        │  审计数据库   │ │ 缓存层    │
        │  (只写)       │ │ Redis集群 │
        │  操作日志     │ │  高频查询  │
        └───────────────┘ └───────────┘

二、核心优化策略

2.1 表设计优化

复制代码
-- 原始设计 vs 优化设计
-- 原始设计(2025年9月)
CREATE TABLE letters (
    id INT AUTO_INCREMENT PRIMARY KEY,
    sender_id INT,
    receiver_id INT,
    content TEXT,
    status VARCHAR(20),
    created_at TIMESTAMP
);

-- 优化设计(2025年12月)
CREATE TABLE letters (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sender_id INT UNSIGNED NOT NULL COMMENT '发送者ID',
    receiver_id INT UNSIGNED NOT NULL COMMENT '接收者ID',
    content MEDIUMTEXT NOT NULL COMMENT '信件内容(加密存储)',
    content_hash CHAR(64) NOT NULL COMMENT 'SHA256内容哈希',
    prison_code CHAR(10) NOT NULL COMMENT '监狱代码',
    letter_type TINYINT NOT NULL DEFAULT 1 COMMENT '1:家属信 2:官方信 3:紧急信',
    status TINYINT NOT NULL DEFAULT 0 COMMENT '0:待审核 1:已发送 2:已送达 3:已阅读',
    priority TINYINT NOT NULL DEFAULT 1 COMMENT '优先级 1-5',
    attachment_count TINYINT NOT NULL DEFAULT 0 COMMENT '附件数量',
    word_count INT UNSIGNED NOT NULL COMMENT '字数统计',
    encryption_key_id VARCHAR(32) COMMENT '加密密钥ID',
    reviewed_by INT UNSIGNED COMMENT '审核人ID',
    reviewed_at DATETIME COMMENT '审核时间',
    sent_at DATETIME COMMENT '发送时间',
    delivered_at DATETIME COMMENT '送达时间',
    read_at DATETIME COMMENT '阅读时间',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at DATETIME COMMENT '软删除时间',
    INDEX idx_sender_status (sender_id, status),
    INDEX idx_receiver_status (receiver_id, status),
    INDEX idx_prison_status (prison_code, status, created_at),
    INDEX idx_created_status (created_at, status),
    UNIQUE INDEX uniq_content_hash (content_hash) COMMENT '防止重复内容'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 
  COMMENT='信件表' 
  ROW_FORMAT=COMPRESSED 
  KEY_BLOCK_SIZE=8;

2.2 分表分库策略

复制代码
class DatabaseSharding {
    /** 基于监狱代码的分表策略 */
    private function getTableName($prisonCode, $yearMonth) {
        // 格式: letters_{省份缩写}_{年月}
        $province = $this->extractProvince($prisonCode);
        $table = "letters_{$province}_{$yearMonth}";
        
        // 每月自动创建新表
        $this->createTableIfNotExists($table);
        
        return $table;
    }
    
    /** 基于用户ID的分库策略 */
    private function getDatabaseByUserId($userId) {
        // 家属用户和服刑用户分离
        $userType = $this->getUserType($userId);
        
        if ($userType === 'FAMILY') {
            $shardId = $userId % 4;  // 家属库分4个
            return "weiai_family_{$shardId}";
        } else if ($userType === 'INMATE') {
            $prisonCode = $this->getPrisonCode($userId);
            $province = $this->extractProvince($prisonCode);
            return "weiai_prison_{$province}";  // 按省份分库
        }
        
        return "weiai_common";
    }
    
    /** 自动创建分表 */
    private function createTableIfNotExists($tableName) {
        $sql = "CREATE TABLE IF NOT EXISTS {$tableName} LIKE letters_template";
        $this->execute($sql);
    }
}

三、查询优化实战

3.1 高频查询优化

复制代码
class QueryOptimizer {
    /** 优化前:N+1查询问题 */
    public function getInmateLettersBad($inmateId) {
        $letters = $this->db->query(
            "SELECT * FROM letters WHERE receiver_id = ?", 
            [$inmateId]
        );
        
        foreach ($letters as &$letter) {
            // 每次循环都查数据库
            $letter['sender_info'] = $this->db->query(
                "SELECT name, relation FROM family WHERE id = ?",
                [$letter['sender_id']]
            );
        }
        
        return $letters;
    }
    
    /** 优化后:JOIN + 缓存 */
    public function getInmateLettersOptimized($inmateId) {
        // 使用JOIN一次性获取
        $sql = "SELECT 
                    l.*,
                    f.name as sender_name,
                    f.relation as sender_relation,
                    f.mobile as sender_mobile
                FROM letters l
                LEFT JOIN family f ON l.sender_id = f.id
                WHERE l.receiver_id = ?
                  AND l.status IN (1, 2, 3)  -- 只查有效状态
                  AND l.deleted_at IS NULL
                ORDER BY l.priority DESC, l.created_at DESC
                LIMIT 100";
        
        $letters = $this->db->query($sql, [$inmateId]);
        
        // 监狱信息批量查询
        $prisonCodes = array_unique(array_column($letters, 'prison_code'));
        $prisons = $this->batchGetPrisons($prisonCodes);
        
        // 合并结果
        foreach ($letters as &$letter) {
            $letter['prison_info'] = $prisons[$letter['prison_code']] ?? null;
        }
        
        return $letters;
    }
    
    /** 批量查询优化 */
    private function batchGetPrisons($prisonCodes) {
        if (empty($prisonCodes)) return [];
        
        // 使用IN查询而不是循环
        $placeholders = implode(',', array_fill(0, count($prisonCodes), '?'));
        $sql = "SELECT * FROM prisons WHERE code IN ({$placeholders})";
        
        $result = $this->db->query($sql, $prisonCodes);
        
        // 转换为code为key的数组
        return array_column($result, null, 'code');
    }
}

3.2 复杂报表查询优化、

复制代码
-- 月度信件统计报表(优化前)
EXPLAIN SELECT 
    DATE(created_at) as day,
    prison_code,
    COUNT(*) as total,
    SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as sent,
    SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as delivered,
    SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as read
FROM letters
WHERE created_at BETWEEN '2025-12-01' AND '2025-12-31'
GROUP BY DATE(created_at), prison_code
ORDER BY day DESC, total DESC;

-- 优化后:使用汇总表
CREATE TABLE letter_daily_stats (
    stat_date DATE NOT NULL COMMENT '统计日期',
    prison_code CHAR(10) NOT NULL COMMENT '监狱代码',
    total_count INT NOT NULL DEFAULT 0 COMMENT '总信件数',
    sent_count INT NOT NULL DEFAULT 0 COMMENT '已发送',
    delivered_count INT NOT NULL DEFAULT 0 COMMENT '已送达',
    read_count INT NOT NULL DEFAULT 0 COMMENT '已阅读',
    avg_process_time INT COMMENT '平均处理时间(秒)',
    PRIMARY KEY (stat_date, prison_code),
    INDEX idx_prison_date (prison_code, stat_date)
) ENGINE=InnoDB COMMENT='信件每日统计表';

-- 定时任务更新汇总表
CREATE EVENT update_letter_stats
ON SCHEDULE EVERY 1 HOUR
DO
BEGIN
    INSERT INTO letter_daily_stats (
        stat_date, prison_code, total_count, 
        sent_count, delivered_count, read_count
    )
    SELECT 
        DATE(created_at) as stat_date,
        prison_code,
        COUNT(*) as total_count,
        SUM(status = 1) as sent_count,
        SUM(status = 2) as delivered_count,
        SUM(status = 3) as read_count
    FROM letters
    WHERE created_at >= DATE_SUB(NOW(), INTERVAL 2 DAY)
      AND created_at < CURDATE()  -- 只处理昨天之前的数据
    GROUP BY stat_date, prison_code
    ON DUPLICATE KEY UPDATE
        total_count = VALUES(total_count),
        sent_count = VALUES(sent_count),
        delivered_count = VALUES(delivered_count),
        read_count = VALUES(read_count);
END;

四、索引优化策略

4.1 智能索引管理

复制代码
class IndexManager {
    /** 自动分析并创建索引 */
    public function optimizeIndexes($table) {
        // 1. 分析现有索引
        $existingIndexes = $this->getTableIndexes($table);
        
        // 2. 分析慢查询日志
        $slowQueries = $this->analyzeSlowLog($table);
        
        // 3. 生成优化建议
        $recommendations = [];
        
        foreach ($slowQueries as $query) {
            $whereColumns = $this->extractWhereColumns($query);
            $orderColumns = $this->extractOrderColumns($query);
            
            // 推荐组合索引
            if (!empty($whereColumns)) {
                $indexName = "idx_" . implode('_', $whereColumns);
                
                if (!isset($existingIndexes[$indexName])) {
                    // 添加排序字段
                    if (!empty($orderColumns)) {
                        $indexColumns = array_merge($whereColumns, $orderColumns);
                    } else {
                        $indexColumns = $whereColumns;
                    }
                    
                    $recommendations[] = [
                        'type' => 'ADD',
                        'name' => $indexName,
                        'columns' => $indexColumns,
                        'estimated_improvement' => $this->estimateImprovement($query)
                    ];
                }
            }
        }
        
        // 4. 删除无用索引
        $unusedIndexes = $this->findUnusedIndexes($table);
        foreach ($unusedIndexes as $index) {
            if ($index['name'] !== 'PRIMARY') {
                $recommendations[] = [
                    'type' => 'DROP',
                    'name' => $index['name'],
                    'reason' => '超过30天未使用'
                ];
            }
        }
        
        return $recommendations;
    }
    
    /** 为监狱信件查询优化的索引策略 */
    public function createPrisonLetterIndexes() {
        // 高频查询模式1:家属查发给服刑人员的信
        $this->db->execute(
            "CREATE INDEX idx_family_inmate ON letters (sender_id, receiver_id, created_at DESC)"
        );
        
        // 高频查询模式2:按监狱+状态+时间查询
        $this->db->execute(
            "CREATE INDEX idx_prison_status_time ON letters (prison_code, status, created_at)"
        );
        
        // 高频查询模式3:审核队列查询
        $this->db->execute(
            "CREATE INDEX idx_review_queue ON letters (status, priority DESC, created_at) WHERE status = 0"
        );
        
        // 覆盖索引:避免回表
        $this->db->execute(
            "CREATE INDEX idx_covering_sender ON letters (sender_id, status, created_at) INCLUDE (receiver_id, prison_code, content_hash)"
        );
    }
}

4.2 分区表策略

复制代码
-- 按时间范围分区(适用于信件表)
ALTER TABLE letters 
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202509 VALUES LESS THAN (202510),
    PARTITION p202510 VALUES LESS THAN (202511),
    PARTITION p202511 VALUES LESS THAN (202512),
    PARTITION p202512 VALUES LESS THAN (202601),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 按监狱代码哈希分区(适用于监狱关联表)
ALTER TABLE prison_relationships
PARTITION BY HASH(MOD(prison_code, 8))
PARTITIONS 8;

五、缓存策略优化

5.1 多层缓存架构

复制代码
class CacheManager {
    private $redis;
    private $localCache = [];
    
    /** 四级缓存策略 */
    public function getWithCache($key, $callback, $ttl = 300) {
        // 1. PHP进程内存缓存(最快)
        if (isset($this->localCache[$key])) {
            return $this->localCache[$key];
        }
        
        // 2. Redis缓存(分布式)
        $cached = $this->redis->get($key);
        if ($cached !== false) {
            $this->localCache[$key] = $cached;
            return $cached;
        }
        
        // 3. MySQL查询(带缓存标记)
        $lockKey = "lock:{$key}";
        if ($this->redis->setnx($lockKey, 1, 5)) { // 获取分布式锁
            try {
                $data = $callback(); // 执行数据库查询
                
                // 4. 写入缓存
                $this->redis->setex($key, $ttl, $data);
                $this->localCache[$key] = $data;
                
                return $data;
            } finally {
                $this->redis->del($lockKey);
            }
        } else {
            // 等待其他进程加载数据
            usleep(100000); // 100ms
            return $this->getWithCache($key, $callback, $ttl);
        }
    }
    
    /** 监狱信息缓存策略 */
    public function getPrisonInfo($prisonCode) {
        $key = "prison:{$prisonCode}";
        
        return $this->getWithCache($key, function() use ($prisonCode) {
            // 查询数据库
            $sql = "SELECT * FROM prisons WHERE code = ?";
            $result = $this->db->query($sql, [$prisonCode]);
            
            if ($result) {
                // 关联查询监狱规则
                $rules = $this->db->query(
                    "SELECT * FROM prison_rules WHERE prison_code = ?",
                    [$prisonCode]
                );
                $result['rules'] = $rules;
            }
            
            return $result;
        }, 3600); // 缓存1小时
    }
    
    /** 批量缓存预热 */
    public function warmUpCache() {
        // 预加载热点数据
        $hotPrisons = $this->getHotPrisons();
        
        foreach ($hotPrisons as $prison) {
            $this->getPrisonInfo($prison['code']);
        }
        
        // 预加载家属常用数据
        $activeFamilies = $this->getActiveFamilies(1000);
        foreach ($activeFamilies as $family) {
            $key = "family:{$family['id']}:inmates";
            $this->getWithCache($key, function() use ($family) {
                return $this->getFamilyInmates($family['id']);
            }, 1800);
        }
    }
}

性能指标与SLA

6.1 性能基准

指标 目标值 监控频率 报警阈值
查询平均响应时间 < 50ms 实时 > 100ms
写入平均响应时间 < 100ms 实时 > 200ms
连接池使用率 < 80% 每分钟 > 90%
缓存命中率 > 95% 每分钟 < 90%
慢查询数量 < 10/分钟 每分钟 > 50/分钟
锁等待时间 < 1秒 实时 > 5秒

6.2 优化成果(实施后对比)

优化项 优化前 优化后 提升
信件查询平均时间 320ms 45ms 7.1倍
监狱信息查询 180ms 缓存命中 2ms 90倍
月度统计报表 12秒 0.8秒 15倍
数据库连接数峰值 850 120 减少86%
存储空间 2.1TB 1.3TB 节省38%

七、最佳实践总结

7.1 微爱帮数据库优化黄金法则

  1. 查询第一原则

    先优化查询,再考虑加硬件

  2. 索引适量原则

    每个表索引不超过5个,联合索引不超过4列

  3. 缓存友好原则

    能缓存的尽量缓存,缓存时间业务化

  4. 分区适时原则

    单表超过1000万行考虑分区

  5. 监控先行原则

    没有监控就没有优化

7.2 特殊业务考虑

由于微爱帮业务的特殊性,我们特别注意:

  1. 监狱数据隔离:不同监狱数据物理隔离

  2. 审计日志完整:所有操作都有迹可循

  3. 敏感信息加密:信件内容加密存储

  4. 合规性优先:优化不得违反监管要求


文档版本 :v2.0
最后更新 :2025年12月
适用环境 :微爱帮生产数据库集群
负责人:数据库团队(

监控面板 :保密
紧急联系人:保密

优化不是一次性的工作,而是持续的实践。在微爱帮,我们相信:
最好的优化,是用户感受不到的流畅体验

相关推荐
倔强的石头_1 小时前
灵活性与高性能兼得:KingbaseES 对 JSON 数据的全面支持解析
数据库
SadSunset1 小时前
(15)动态SQL中的if,foreach和一些其他的常用标签
数据库·python·sql
问道飞鱼1 小时前
【数据库知识】MySQL 数据库备份与还原详细解读
数据库·mysql·备份·还原
马克学长1 小时前
SSM实验室设备管理系统8gr9f(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·实验室设备管理·设备预约
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue在线考试管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
DreamNotOver1 小时前
使用 Django 测试脚本验证用户角色与权限:自动化测试用户仪表盘访
数据库·mysql·django·sqlite
萝卜青今天也要开心1 小时前
2025年下半年系统架构设计师考后分享
java·数据库·redis·笔记·学习·系统架构
子夜江寒2 小时前
MySQL 安装与配置指南(CentOS 7)
数据库·mysql·centos
DemonAvenger2 小时前
Redis集群架构详解:从单机到分布式的扩展之路
数据库·redis·性能优化