You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止PHP会话关闭?解决MySQL RDS孤儿表性能问题

解决方案

1. 用MySQL事件调度器定时清理孤儿表(零额外成本)

既然用的是MySQL RDS,直接用它自带的事件调度器就能解决,不用加额外服务:

  • 第一步:给每张用户专属表加last_activity字段,设置自动更新:
    ALTER TABLE user_xxx_table ADD COLUMN last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
    
    这个字段会在用户操作表时自动刷新为当前时间,记录用户最后活跃节点。
  • 第二步:开启MySQL事件调度器(RDS需先在参数组将event_scheduler设为ON,重启实例生效),然后创建定时清理事件,比如每小时清理一次超过24小时无活动的表:
    DELIMITER //
    CREATE EVENT clean_orphan_tables
    ON SCHEDULE EVERY 1 HOUR
    DO
    BEGIN
      DECLARE done INT DEFAULT FALSE;
      DECLARE table_name VARCHAR(255);
      DECLARE cur CURSOR FOR 
        SELECT table_name FROM information_schema.tables 
        WHERE table_schema = '你的数据库名' 
          AND table_name LIKE 'user_%'
          AND (SELECT last_activity FROM `table_name`) < DATE_SUB(NOW(), INTERVAL 24 HOUR);
      DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
      OPEN cur;
      read_loop: LOOP
        FETCH cur INTO table_name;
        IF done THEN
          LEAVE read_loop;
        END IF;
        SET @drop_sql = CONCAT('DROP TABLE IF EXISTS `', table_name, '`');
        PREPARE stmt FROM @drop_sql;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
      END LOOP;
      CLOSE cur;
    END //
    DELIMITER ;
    
  • 第三步:在CodeIgniter 3的全局钩子(比如post_controller)里,用户每次请求后手动刷新对应表的last_activity(如果用户没操作表但有页面请求的话):
    // hooks.php 配置
    $hook['post_controller'] = array(
        'class'    => 'ActivityHook',
        'function' => 'update_last_activity',
        'filename' => 'ActivityHook.php',
        'filepath' => 'hooks',
    );
    
    // hooks/ActivityHook.php
    class ActivityHook {
        public function update_last_activity() {
            $CI =& get_instance();
            $user_id = $CI->session->userdata('user_id');
            if ($user_id) {
                $table = "user_{$user_id}_main"; // 选一张用户核心表即可
                $CI->db->query("UPDATE `{$table}` SET last_activity = NOW()");
            }
        }
    }
    

优点:零额外成本,完全基于现有RDS;缺点:清理有延迟(最多延迟1小时),需要修改现有表结构。

2. 重构表结构:放弃用户专属表,改用带用户标识的共享表(最优解)

从根源解决表数量爆炸的问题:把6张用户专属表改成6张共享表,每张表加user_id字段,用户数据按user_id区分:

  • 比如原来的user_123_order改成orders表,添加user_id INT NOT NULL字段,给user_id加索引。
  • 用户注销时,执行DELETE FROM orders WHERE user_id = {$user_id};,批量删除该用户的所有数据。
  • 同样用定时任务清理过期数据:比如删除30天未登录用户的所有数据:
    DELETE FROM orders WHERE user_id IN (SELECT id FROM users WHERE last_login < DATE_SUB(NOW(), INTERVAL 30 DAY));
    -- 对其他5张表执行同样操作
    

优点:彻底避免孤儿表问题,数据库性能更稳定,维护成本低;缺点:需要重构现有代码的查询逻辑(比如原来查user_123_order,现在要加WHERE user_id = 123条件)。

3. 前端监听页面关闭事件,触发异步注销(补充方案)

用浏览器的beforeunload事件配合navigator.sendBeacon发送清理请求,覆盖大部分正常关闭标签页的场景:

  • 前端全局JS代码:
    window.addEventListener('beforeunload', function() {
      const userId = document.querySelector('meta[name="user-id"]').content;
      const formData = new FormData();
      formData.append('user_id', userId);
      // sendBeacon会在页面卸载时异步发送,不会阻塞页面关闭
      navigator.sendBeacon('/clean-user-tables', formData);
    });
    
  • CodeIgniter后端接口:
    public function clean_user_tables() {
      $user_id = $this->input->post('user_id');
      if ($user_id) {
        $tables = [
          "user_{$user_id}_table1",
          "user_{$user_id}_table2",
          // 其他4张表
        ];
        foreach ($tables as $table) {
          $this->db->query("DROP TABLE IF EXISTS `{$table}`");
        }
      }
    }
    

优点:能及时清理大部分正常关页用户的表;缺点:无法覆盖浏览器崩溃、网络中断等极端情况,只能作为辅助手段。


内容的提问来源于stack exchange,提问作者richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 11:35:58