thinkphp5水平分割表partition,以及查询操作

前言

先交代下背景,在一个项目中,有一个数据表有水平分表的需求。当时想找到一种方法,把对数据库的操作,写到一个模型里,通过去换模型属性中的table来达到代码不变操作的数据表变化的效果。
我们都知道,模型要想关联数据表的话,有两中方式,第一种就是将模型名和数据表一致。这样模型就会默认关联到名字对应的数据表。第二种就是定义模型的 protected $table 来指定表明。
添加链接描述

模型初始化

ThinkPHP5内置了partition方法,可用于实现简单的分表。新增,修改,删除,查询单条数据时,用parition方法都可以轻松搞定,因为这些操作有一个共同的特点,就是能事先明确的知道,我要操作的是哪一条记录。但有一个需求,ThinkPHP5似乎没有解决,比如当一个大表,被拆分成若干个子表时,如何根据相关条件及排序获取分页数据。这种需求场景下,由于事先并不知道哪些数据会出现在第一页,哪些数据会出现在第二页,这些根据检索条件动态匹配的列表数据,该如何查询呢?

模型层
<?php
namespace app\common\model;
use think\Model;
class WalletLog extends Model
{
    protected $table = 'tb_walletlog';
    protected $pk = 'walletid';
    
    private function getRule(){
        return [
            'type' => 'mod', // 分表方式
            'num'  => 3     // 分表数量
        ];
    }
    
    public function saveData($data, $user_id){
        
        return $this->partition(['user_id' => $user_id], "user_id", $this->getRule())->insert($data);
    }
    
    public function getAll($where, $field = "*", $user_id){
        return $this->partition(['user_id' => $user_id], "user_id", $this->getRule())->where($where)->field($field)->select();
    }
    
   //查询所有分割表
    public function getAll2(){
        return $this->partition('', '', $this->getRule())->select();
    }
}

分库分表查询分表与其他表关联查询 join 不好使用(各种sql 报错)估计不支持

解决方案:

插入使用分表操作,进行分割数据,查询使用普通联合/聚合查询

1、模型层调整 查询
namespace application\workflow\model;
use think\Model;
class Init_Model extends Model
{
    protected $pk = 'id';
    //初始化表名
    public function __construct($name = '')
    {
        if (!empty($name)) {
            $this->name = $name;
        }
    }
}

使用时:$model = new init_model(‘test’); //成功改变了数据表
$model->get(); //操作时二次走__construct成空白,还原成没有表名;
请问有内置方法动态改变表名吗,对于动态管理的东东很须要这个功能呀
前人栽树,后人挖坑,哎

2、 执行sql 查询(拼接表名称来实现查询)
select count(1) as total from .$tables['countSql']
3、Db模型函数查询

注意:不要使用任何 SQL 语句中会出现的关键字当表名、字段名,例如 order 等。会导致数据模型拼装 SQL 语句语法错误。

thinkphp5 看云文档 partition 方法用于是数据库水平分表
partition 方法用法如下:

// 用于写入
$data = [
    'user_id'   => 110,
    'user_name' => 'think'
];

$rule = [
    'type' => 'mod', // 分表方式
    'num'  => 10     // 分表数量
];
Db::name('log')
    ->partition(['user_id' => 110], "user_id", $rule)
    ->insert($data);
    
// 用于查询
Db::name('log')
    ->partition(['user_id' => 110], "user_id", $rule)
    ->where(['user_id' => 110])
    ->select();
4、闭包用法:

每个union方法相当于一个独立的SELECT语句。MySQL通过创建并填充临时表的方式来执行union查询。除非确实要消除重复的行,否则建议使用union all。原因在于如果没有all这个关键词,MySQL会给临时表加上distinct选项,这会导致对整个临时表的数据做唯一性校验,这样做的消耗相当高。

Db::field('name')
      ->table('think_user_0')
      ->union('SELECT name FROM think_user_1')
      ->union('SELECT name FROM think_user_2')
      ->select();
//或者下面这种用法
Db::field('name')
      ->table('think_user_0')
      ->union(['SELECT name FROM think_user_1','SELECT name FROM think_user_2'])
      ->select();
//支持UNION ALL 操作,例如:
Db::field('name')
      ->table('think_user_0')
      ->union(['SELECT name FROM think_user_1','SELECT name FROM think_user_2'],true)
      ->select();

其他案例

应用ThinkPHP内置的分表算法处理百万级用户数据.(适合流水类记录表)

数据表:
  house_member_0
  house_member_1
  house_member_2
  house_member_3
  模型中//
  class MemberModel extends AdvModel {
  protected $partition = array('field'=>'username','type'=>'id','num'=>'4');
  public function getDao($data=array()) {
  $data = empty($data) ? $_POST : $data;
  $table = $this->getPartitionTableName($data);
  return $this->table($table);
  }
  }

  方法中
  class MemberAction extends BaseAction {
  public function login() {
  if($this->isPost()) {
  $this->validToken();
  $dao = D('Member')->getDao();
  $res = $dao->where('username = '.$_POST['username'])->find();

  // output 为自定义方法

  // $isAjax - bool
  $this->output(false);
  }
  $this->display();
  }
  }

  /**
  +----------------------------------------------------------
  * 得到分表的的数据表名
  +----------------------------------------------------------
  * @access public
  +----------------------------------------------------------
  * @param array $data 操作的数据
  +----------------------------------------------------------
  * @return string
  +----------------------------------------------------------
  */
  public function getPartitionTableName($data=array()) {
  // 对数据表进行分区
  if(isset($data[$this->partition['field']])) {
  $field = $data[$this->partition['field']];
  switch($this->partition['type']) {
  case 'id':
  // 按照id范围分表
  $step = $this->partition['expr'];
  $seq = floor($field / $step)+1;
  break;
  case 'year':
  // 按照年份分表
  if(!is_numeric($field)) {
  $field = strtotime($field);
  }
  $seq = date('Y',$field)-$this->partition['expr']+1;
  break;
  case 'mod':
  // 按照id的模数分表
  $seq = ($field % $this->partition['num'])+1;
  break;
  case 'md5':
  // 按照md5的序列分表

  $seq = (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;
  break;
  default :
  if(function_exists($this->partition['type'])) {
  // 支持指定函数哈希
  $fun = $this->partition['type'];
  $seq = (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;
  }else{
  // 按照字段的首字母的值分表
  $seq = (ord($field{0}) % $this->partition['num'])+1;
  }
  }
  return $this->getTableName().'_'.$seq;
  }else{
  // 当设置的分表字段不在查询条件或者数据中
  //进行联合查询,必须设定 partition['num']
  $tableName = array();
  for($i=0;$i<$this->partition['num'];$i++)
  $tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.$i;
  $tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;
  return $tableName;
  }
  }

温馨提示:
其中 partition 方法指定要查询的分表,where 方法则是查询条件。
需要注意的是,分表可能会给应用带来更高的维护成本和查询复杂度,需要在实际开发中根据业务需要进行选择
复杂模型实现不了的业务逻辑只能用原生sql 来实现。(团队封装的模型基本够用,大数据项目也不会用轻量级框架)有更好的分表查询方法可以欢迎留言。用到那、学到哪!!!

其他文档

ThinkPHP中的分表使用

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闰土小蒋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值