记得去年在项目中遇见一个比较算正常的需求:订单列表中要出`本系统的订单`与`第三方订单`(走接口方式推送到我们系统中来),包括正常where条件查询,分页,排序要完全符合,因为第三方的订单字段与本系统的字段不一,我这边想的是把第三方多出字段加入我们订单表中字段来实现。但是我们架构师说不这么做,用PgSQL的继承把它们两个表隔离开,继承一个父表即可。当时我按照架构师要求完成了该需求,现在再回头仔细看这个PgSQL中的表继承(INHERITS)到底是什么,能干些什么事?
介绍
关键字:“inherits”,继承是面向对象数据库中的概念,展示了数据库设计的新的可能性(代码层面类似)。
2. 创建表
CREATE TABLE IF NOT EXISTS "order" (
id serial,
uid int not null default 0, -- 创建人
order_sn varchar(60) not null default '', -- 订单编号
create_at timestamp(0) not null default now(), -- 创建时间
primary key(id)
);
CREATE TABLE IF NOT EXISTS "order_202001" (
) inherits ("order");
CREATE TABLE IF NOT EXISTS "order_202002" (
) inherits ("order");
这边都不以项目实际场景做例子了,就简单的一个表order订单表,字段为id,uid,order_sn,create_at字段,剩下两个表没有额外的字段(可以建属于自己独立的字段),直接继承父表。
3.测试
3.1假如:2020年1月1日下了一个订单,2020年2月1日下了一个订单。
insert into order_202001 (uid,order_sn,create_at) values (1,'998','2020-01-01 12:10:12');
insert into order_202002 (uid,order_sn,create_at) values (1,'999','2020-02-01 12:10:12');
3.2开始查询:
hzl_test=# select * from "order";
id | uid | order_sn | create_at
----+-----+----------+---------------------
1 | 1 | 998 | 2020-01-01 12:10:12
2 | 1 | 999 | 2020-02-01 12:10:12
(2 rows)
hzl_test=# select * from "order_202001";
id | uid | order_sn | create_at
----+-----+----------+---------------------
1 | 1 | 998 | 2020-01-01 12:10:12
(1 row)
hzl_test=# select * from "order_202002";
id | uid | order_sn | create_at
----+-----+----------+---------------------
2 | 1 | 999 | 2020-02-01 12:10:12
(1 row)
3.3我们现在试图在父表增加一个订单状态status字段,在查询下各自表的变化。
alter table "order" ADD COLUMN IF NOT EXISTS status int NOT NULL DEFAULT 0;
hzl_test=# select * from "order";
id | uid | order_sn | create_at | status
----+-----+----------+---------------------+--------
1 | 1 | 998 | 2020-01-01 12:10:12 | 0
2 | 1 | 999 | 2020-02-01 12:10:12 | 0
(2 rows)
hzl_test=# select * from "order_202001";
id | uid | order_sn | create_at | status
----+-----+----------+---------------------+--------
1 | 1 | 998 | 2020-01-01 12:10:12 | 0
(1 row)
hzl_test=# select * from "order_202002";
id | uid | order_sn | create_at | status
----+-----+----------+---------------------+--------
2 | 1 | 999 | 2020-02-01 12:10:12 | 0
(1 row)
3.4 又接到需求2月份受疫情影响,2月份的订单中要标示这订单是否路过“武汉” is_wuhan字段 1过 0不过,
我们再查看下三个表的区别
alter table "order_202002" ADD COLUMN IF NOT EXISTS is_wuhan int NOT NULL DEFAULT 0;
hzl_test=# select * from "order";
id | uid | order_sn | create_at | status
----+-----+----------+---------------------+--------
1 | 1 | 998 | 2020-01-01 12:10:12 | 0
2 | 1 | 999 | 2020-02-01 12:10:12 | 0
(2 rows)
hzl_test=# select * from "order_202001";
id | uid | order_sn | create_at | status
----+-----+----------+---------------------+--------
1 | 1 | 998 | 2020-01-01 12:10:12 | 0
(1 row)
hzl_test=# select * from "order_202002";
id | uid | order_sn | create_at | status | is_wuhan
----+-----+----------+---------------------+--------+----------
2 | 1 | 999 | 2020-02-01 12:10:12 | 0 | 0
(1 row)
好了,看到这小伙伴们估计都明白了,这不是我们常说到的表的水平拆分吗?在PgSQL中水平拆分如此简单。
4. 代码实现:我们发现这块发生变化只是表名,使用OrderModel可查询全部月订单,指定月份代码如下
<?php
namespace core;
class Order {
// 实例数组
protected static $instanceArr = [];
// 订单表后缀order_{$ym}, 比如:order_201912
protected $ym = '';
// 是否存在动态class文件
private $existClassFile = false;
// 单例构造方法
public static function createInstance($ym)
{
if (empty($ym)) {
throw new \Exception("param 'ym' not null,if you need get all order,please use 'OrderModel'");
}
if (empty(self::$instanceArr[$ym])) {
self::$instanceArr[$ym] = new self($ym);
}
return self::$instanceArr[$ym];
}
protected function __construct($ym)
{
$this->ym = $ym;
}
// 使用魔术方法去调用动态模型中的方法
public function __call($method, $args)
{
$class = $this->loadClass();
return call_user_func_array([$class, $method], $args);
}
// 加载动态class
protected function loadClass()
{
$class ='order_' . $this->ym;
if (!$this->existClassFile) {
$file = \Yii::$app->getRuntimePath().'/' . $class . '.php'; // Yii运行时产生的文件
if (!is_file($file)) {
$this->writeClass($file, $class);
}
@include $file;
$this->existClassFile = true;
}
return $class;
}
// 创建动态class文件
public function writeClass($file, $class)
{
if (!is_dir(dirname($file))) {
mkdir(dirname($file), 0755);
}
$tpl = "<?php class %s extends \yii\db\ActiveRecord { public static function tableName() { return '%s';} }";
$fileText = sprintf($tpl, $file, $class);
file_put_contents($file, sprintf($tpl, $class, $class));
}
}
// controller中调用
\core\Order::createInstance('202001')->deleteAll(); // 删除202001月的全部订单
5.总结
1.父表定义表子表会继承,父表的话尽量建一些通用的字段。
2.父表定义的字段,子表无法修改(ALTER操作)。
3.这样做水平拆分的话会减少数据库的增删改查的压力。
作者理解的浅,如有错误欢迎指出。
转发请注明出处!