前言:
公司面临着重构,数据大概在400万左右。新旧系统的数据库设计表 全变了,表与表之间的关联关系也变了,有些甚至需要请求第三方接口来获得数据。
依赖:
迁移逻辑:
旧数据库-->新数据库-->第三方平台(如淘宝等)
试验过程花的时间
- 第一次,从旧数据库迁移到新数据库,400多万数据,花了9小时
- 第二次,从旧数据库迁移到新数据库,400多万数据,花了80分钟
- 第三次,从旧数据库迁移到新数据库,400多万数据,花了20多分钟
从旧数据迁移-->新数据库 优化方案过程
//每次只获取1000条,并且插入到新数据库,如果1000改为更大,那可能造成memory_limit的问题
class MigrateGoodSpecCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string 脚本命令
*/
protected $signature = 'migrate:good_spec';
public function handle(){
DB::connect('old_旧数据库')->from('good_spec')->chunk(1000, function($data){
//查找旧数据的分类,品牌,仓库,库存,bc,cc税则等其他关联表的相关数据
//组织好数据$insertData,直接数据库插入
GoodSpecModel::insert($insertData);
});
}
}
class MigrateGoodSpecCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string 脚本命令
*/
protected $signature = 'migrate:good_spec';
public function handle(){
DB::connect('old_旧数据库')->from('good_spec')->chunk(1000, function($data){
//查找旧数据的分类,品牌,仓库,库存,bc,cc税则等其他关联表的相关数据
//组织好数据$insertData,放到 MigrateGoodSpecDataJob 队列任务中
MigrateGoodSpecDataJob::dispatch($insertData)->onConnection('redis')->onQueue('migrate');
});
}
}
//实现ShouldQueue接口,可进行队列任务
class MigrateGoodSpecDataJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use LogTrait;
protected $insertData;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($insertData)
{
$this->insertData = $insertData;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
GoodSpecModel::insert($data);
}
}
class MigrateGoodSpecCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string 脚本命令
*/
protected $signature = 'migrate:good_spec {limit}';
public function handle(){
$arr = $this->getBetweenId();
if (empty($arr['min']) || empty($arr['max']))
{
$this->log('数据超过范围:'.json_encode($arr));
}
DB::connect('old_旧数据库')->from('good_spec')
->where('Id', '>=', $arr['min'])
->where('Id', '<', $arr['max'])
->chunk(1000, function($data){
//查找旧数据的分类,品牌,仓库,库存,bc,cc税则等其他关联表的相关数据
//组织好数据$insertData,放到 MigrateGoodSpecDataJob 队列任务中
MigrateGoodSpecDataJob::dispatch($insertData)->onConnection('redis')->onQueue('migrate');
});
}
public function getBetweenId()
{
//maxId=0,5000000
$str = $this->argument('limit');
$arr = explode(',', $str);
if (count($arr) !== 2){ throw \Exception('limit参数错误:'.$str); }
$maxLimit = (int)$arr[0]+(int)$arr[1];
$minId = DB::connect('old_旧数据库')->from('good_spec')->orderBy('Id', 'asc')->offset((int)$arr[0])->limit(1)->value('Id');
$maxId = DB::connect('old_旧数据库')->from('good_spec')->orderBy('Id', 'asc')->offset($maxLimit)->limit(1)->value('Id');
if(empty($maxId))
{
$maxId = GoodsModel::orderBy('Id', 'desc')->where('Id', '>', $minId)->value('Id');
$maxId = $maxId +1;
}
return [
'min' => $minId,
'max' => $maxId
];
}
}
只能执行一次命令: php artisan migrate:good_spec
思考:
这个命令,只能 读取完,然再写数据,单个线程实在太慢了,因此构想:我们可以边读边写,两者互相解耦,提高迁移速度
只能执行一次读取命令: php artisan migrate:good_spec
可以执行多个写入命令类似多线程,开多个终端执行: php artisan queue:work redis –queue=migrate
思考:
在 边读取边写的时候,发现 当 写入200万数据时,读取的速度大大降低。
因此应该是 先读取完数据放到队列,再执行写入,则提高迁移速度。
由于读取命令只能执行一次,为了提高速度,更改了 数据范围性命令,可利用伪多线程原理同时读取数据。
也就是 可以同时读取不同的数据,写入不同的读取完的数据,因此有第三次试验
在这里 每一个命令,都会执行50万条读取, 400万数据分为以下命令
php artisan migrating:good_specifications_ext 0,500000
php artisan migrating:good_specifications_ext 500000,500000
php artisan migrating:good_specifications_ext 1000000,500000
php artisan migrating:good_specifications_ext 1500000,500000
php artisan migrating:good_specifications_ext 2000000,500000
php artisan migrating:good_specifications_ext 2500000,500000
php artisan migrating:good_specifications_ext 3000000,500000
php artisan migrating:good_specifications_ext 3500000,500000
php artisan migrating:good_specifications_ext 4000000,500000
还可以执行多个 写入数据库命令, 开多个终端执行: php artisan queue:work redis –queue=migrate
思考:
觉得每一个命令读取50万条,还能在优化,利用多线程原理。您还有其他更优方案吗?欢迎在评论区。
由于依赖redis,虽然400万数据 花了2-3g内存,但是成本还是挺高。