thinkphp5.1水平分表实践(一)

在thinkphp5.1中可以使用partition方法进行水平分表功能,但其分表功能较简单,不适用某些特殊场景。其在TP中的实现逻辑如下:

文件路径:thinkphp\library\think\db\Query.php   (555行)

 /**
     * 得到分表的的数据表名
     * @access public
     * @param  array  $data  操作的数据
     * @param  string $field 分表依据的字段
     * @param  array  $rule  分表规则
     * @return array
     */
    public function getPartitionTableName($data, $field, $rule = [])
    {
        // 对数据表进行分区
        if ($field && isset($data[$field])) {
            $value = $data[$field];
            $type  = $rule['type'];
            switch ($type) {
                case 'id':
                    // 按照id范围分表
                    $step = $rule['expr'];
                    $seq  = floor($value / $step) + 1;
                    break;
                case 'year':
                    // 按照年份分表
                    if (!is_numeric($value)) {
                        $value = strtotime($value);
                    }
                    $seq = date('Y', $value) - $rule['expr'] + 1;
                    break;
                case 'mod':
                    // 按照id的模数分表
                    $seq = ($value % $rule['num']) + 1;
                    break;
                case 'md5':
                    // 按照md5的序列分表
                    $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1;
                    break;
                default:
                    if (function_exists($type)) {
                        // 支持指定函数哈希
                        $value = $type($value);
                    }

                    $seq = (ord(substr($value, 0, 1)) % $rule['num']) + 1;
            }

            return $this->getTable() . '_' . $seq;
        }
        // 当设置的分表字段不在查询条件或者数据中
        // 进行联合查询,必须设定 partition['num']
        $tableName = [];
        for ($i = 0; $i < $rule['num']; $i++) {
            $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1);
        }

        return ['( ' . implode(" UNION ", $tableName) . ' )' => $this->name];
    }

 

仿照上面的实现逻辑,我实现了一个demo:

1.创建测试数据表

-- 用户信息
CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 聊天内容
CREATE TABLE `t_chat` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(10) unsigned NOT NULL,
  `content` varchar(100) NOT NULL,
  `create_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

 

2.application目录下创建traits目录,并在该目录下创建Partition

<?php

namespace app\traits;

trait Partition
{
    /**
     * 判断数据表是否存在
     * @param string $tableName 表名
     */
    protected function tableExists($tableName)
    {
        $result = $this->query("SHOW TABLES LIKE '{$tableName}'");
        return !empty($result);
    }

    /**
     * 创建分表
     * @param string $tableName 分表表名
     */
    protected function createPartitionTable($tableName)
    {
        $table = $this->getTable();
        $result = $this->query("SHOW CREATE TABLE {$table}");
        $createSql = $result[0]['Create Table'];
        $createSql = str_replace($table, $tableName, $createSql);
        $this->execute($createSql);
    }

    /**
     * 设置分表的的数据表名
     * 按业务、年月设置分表信息,进行后续CURD操作
     * @param string $businessType 业务类型
     * @param string $startTime 开始时间,只传开始时间时用以insert、update、delete操作;同时传结束时间用以select操作
     * @param string $endTime 结束时间
     * @param string $alias 表名别名,用以select操作时各表union后的别名,默认使用模型的name属性
     */
    public function partitionYm($businessType, $startTime, $endTime = null, $alias = null)
    {
        // 开始和结束时间都不传时返回自身
        if (empty($startTime) && empty($endTime)) {
            return $this;
        }

        if (empty($endTime)) {
            $seq = date('Ym', strtotime($startTime));
            $tableName = $this->getTable() . ($businessType ? ('_' . $businessType) : '') . '_' . $seq;
            if (!$this->tableExists($tableName)) {
                $this->createPartitionTable($tableName);
            }
        } else {
            $startDate = new \DateTime($startTime);
            $endDate = new \DateTime($endTime);
            $endDate->modify('last day of this month');

            $interval = $startDate->diff($endDate, true);

            $startTime = strtotime($startTime);
            $startYM = date('Ym', $startTime);
            $tables = [];
            for ($i = 0; $i <= $interval->m; $i++) {
                $table = $this->getTable() . ($businessType ? ('_' . $businessType) : '') . '_' . $startYM;
                if (!$this->tableExists($table)) {
                    continue;
                }
                $tables[] = "SELECT * FROM {$table}";
                $startTime = strtotime("+1 month", $startTime);
                $startYM = date('Ym', $startTime);
            }

            // 时间区间内没有相应的数据表时返回自身
            $tableName = '(' . (empty($tables) ? ('SELECT * FROM ' . $this->getTable()) : implode(" UNION ", $tables)) . ') ' . ($alias ?: $this->name);
            //dump($tableName);
        }

        $this->table = $tableName;
        return $this;
    }
}

 

3.Chat模型里使用Partition

模型路径:application\model\Chat.php

<?php

namespace app\model;

use app\traits\Partition;

class Chat extends Base
{
    use Partition;
}

 

4.控制器使用Chat模型

控制器路径:application\index\controller\Index.php

<?php

namespace app\index\controller;

use app\model\Chat as ChatModel;

class Index
{
    public $businessType = 1;
    public function index()
    {
        $model = new ChatModel;
        $res = $model->partitionYm($this->businessType, '2024-06-25', '2024-09-01', 'chat')
            ->leftJoin('user u', 'chat.uid = u.id')
            ->field(['chat.*', 'u.name'])
            ->order(['chat.create_time' => 'desc', 'chat.id' => 'desc'])
            ->select();
        dump($res);

        return 'index';
    }

    public function save()
    {
        $time = date('Y-m-d H:i:s');
        $data = [
            'uid'   => 2,
            'content' => 'think',
            'create_time' => $time,
        ];


        $model = new ChatModel;
        $model->partitionYm($this->businessType, $time)->insert($data);
        return 'save';
    }
}

 

posted @ 2024-07-15 14:55  tros  阅读(270)  评论(0)    收藏  举报