批量数据迁移

9 篇文章 1 订阅

前言:

公司面临着重构,数据大概在400万左右。新旧系统的数据库设计表 全变了,表与表之间的关联关系也变了,有些甚至需要请求第三方接口来获得数据。

依赖:

  1. php7
  2. laravel 5.5+
  3. redis

迁移逻辑:

旧数据库-->新数据库-->第三方平台(如淘宝等)

试验过程花的时间

  • 第一次,从旧数据库迁移到新数据库,400多万数据,花了9小时
  • 第二次,从旧数据库迁移到新数据库,400多万数据,花了80分钟
  • 第三次,从旧数据库迁移到新数据库,400多万数据,花了20多分钟

 

从旧数据迁移-->新数据库 优化方案过程

  • 第一次, 用laravel的console命令 编辑了一个文件,利用chunk特性如:

//每次只获取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);

        });
    }
 
}

 

  • 第二次,进行读写分离,先把读取到的数据 放到redis队列中,然后在写入到数据库
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内存,但是成本还是挺高。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值