高性能PHP框架webman实现MySQL按月分表存储日志

概述

在实际的应用中,我们通常会遇到数据库表数据量大的情况,特别是涉及大量数据的日志表,数据量会迅速积累,如果将所有数据都存储在同一张表中,会使得查询和操作效率变得低下。

而将数据按照时间进行分表,可以减少单表数据量,提高查询效率,同时方便数据归档和管理。为了更好的管理和优化数据,我们可以对数据库表进行按月分表操作。本文将介绍如何在MySQL中对日志表进行按月分表的操作。

思路

按月分表是每个月的开始都要建立一张以月为单位的新表来存储这一个月的数据。首先需要设计一张表的表名是一直不变的,方便按月分表来复制使用来存储新的一个月的数据的。

假设现在有一张默认日志表resty_log,按月分表首先需要将resty_log表字段结构进行复制即可。

实现

表设计

默认日志表resty_log

CREATE TABLE `resty_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
  `method` varchar(12) NOT NULL COMMENT '请求方式',
  `path` varchar(128) NOT NULL COMMENT '请求路径',
  `message` varchar(255) NOT NULL COMMENT '消息',
  `ip` varchar(32) NOT NULL COMMENT 'ip',
  `params` text NOT NULL COMMENT '请求参数',
  `status` int(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态码',
  `create_time` int(11) unsigned NOT NULL COMMENT '时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='日志表';

分表核心语句

select COUNT(`TABLE_NAME`) as tmp_resty_log from `INFORMATION_SCHEMA`.`TABLES` where `TABLE_SCHEMA`= 'demo.webman.tinywan.com' and `TABLE_NAME`='resty_log_202410'; 

实现技术栈

这里使用webman框架和ThinkORM来实现分表操作

webman框架:webman是一款基于workerman开发的高性能HTTP服务框架。webman用于替代传统的php-fpm架构,提供超高性能可扩展的HTTP服务。你可以用webman开发网站,也可以开发HTTP接口或者微服务。官方文档:https://www.workerman.net/webman

ThinkORM类库:ThinkORM是一个基于PHP和PDO的数据库中间层和ORM类库,早期作为ThinkPHP的一个核心组件现已独立出来,以优异的功能和突出的性能著称,提供了更优秀的性能和开发体验,最新版本要求PHP8.0+。官方文档:https://doc.thinkphp.cn/@think-orm/default.html

代码实现

安装webman

composer create-project workerman/webman

安装ThinkORM插件

composer require -W webman/think-orm

新建模型文件RestyLogModel.php

<?php
/**
 * @desc 系统日志模型
 * @author Tinywan(ShaoBo Wan)
 */
declare(strict_types=1);

namespace app\model;

use think\db\exception\BindParamException;
use think\facade\Db;
use think\Model;

class RestyLogModel extends Model
{
    /**
     * 设置当前模型对应的完整数据表名称
     * @var string
     */
    protected $table = 'resty_log';

    /**
     * @desc 获取按月分表表名
     * @return string
     * @throws BindParamException
     * @author Tinywan(ShaoBo Wan)
     */
    public static function getTableName(): string
    {
        $time = time();
        $tableName = self::getTable() . '_';
        $year = date('Y', $time);
        $month = date('m', $time);
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
        $tableName .= $year . $month;

        $sql = "select COUNT(`TABLE_NAME`) as tmp_resty_log from `INFORMATION_SCHEMA`.`TABLES` where `TABLE_SCHEMA`= '" . \config('thinkorm.connections.mysql.database') . "' and `TABLE_NAME`='" . $tableName . "'";
        $query = Db::query($sql);
        if (empty($query[0]['tmp_resty_log']) || $query[0]['tmp_resty_log'] == 0) {
            self::_createTable($tableName);
        }
        return $tableName;
    }

    /**
     * @desc 创建表
     * @param string $table
     * @throws BindParamException
     * @author Tinywan(ShaoBo Wan)
     */
    private static function _createTable(string $table): void
    {
        $sql = "
            CREATE TABLE `" . $table . "` (
              `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
              `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
              `method` varchar(12) NOT NULL COMMENT '请求方式',
              `path` varchar(128) NOT NULL COMMENT '请求路径',
              `message` varchar(255) NOT NULL COMMENT '消息',
              `ip` varchar(32) NOT NULL COMMENT 'ip',
              `params` text NOT NULL COMMENT '请求参数',
              `status` int(4) unsigned NOT NULL DEFAULT '0' COMMENT '状态码',
              `create_time` int(11) unsigned NOT NULL COMMENT '时间',
              PRIMARY KEY (`id`) USING BTREE,
              KEY `idx_user_id` (`user_id`)
            ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='日志表'";
        Db::execute($sql);
    }
}

写入日志操作

public function recordLog()
{
    $insertData = [
        'user_id' => time(),
        'method' => request()->method(),
        'path' => request()->uri(),
        'message' => '用户登录日志',
        'ip' => request()->getRealIp(),
        'params' => json_encode(request()->all(),JSON_UNESCAPED_UNICODE),
        'status' => response()->getStatusCode(),
        'create_time' => time()
    ];
    \think\facade\Db::table(RestyLogModel::getTableName())->insert($insertData);
}

启动wenman

/var/www/demo.webman.tinywan.com # php start.php start
Workerman[start.php] start in DEBUG mode
---------------------------------------------------------------- WORKERMAN -----------------------------------------------------------------
Workerman version:4.1.15          PHP version:8.3.9           Event-Loop:\Workerman\Events\Select
----------------------------------------------------------------- WORKERS ------------------------------------------------------------------
proto   user            worker                                                    listen                      processes    status
tcp     root            webman                                                    http://0.0.0.0:8217         8             [OK] 
tcp     root            monitor                                                   none                        1             [OK] 
tcp     root            plugin.workbunny.webman-coroutine.coroutine-web-server    http://[::]:8218            2             [OK] 
--------------------------------------------------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

请求http://127.0.0.1:8217/index/index?name=Tinywan模拟写入日志

图片

图片

来源:Tinywan 开源技术小栈

THE END