大量数据迁移

zhazhaji的博客

前言:

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

依赖:

  1. php7
  2. laravel 5.5+
  3. redis

迁移逻辑:

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

试验过程花的时间

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

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

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

//每次只获取1000条,并且插入到新数据库,如果1000改为更大,那可能造成memory_limit的问题

  1. class MigrateGoodSpecCommand extends Command

  2. protected $signature = 'migrate:good_spec';

  3. public function handle(){

  4. DB::connect('old_旧数据库')->from('good_spec')->chunk(1000, function($data){

  5.   GoodSpecModel::insert($insertData);

  • 第二次,进行读写分离,先把读取到的数据 放到redis队列中,然后在写入到数据库
  1. class MigrateGoodSpecCommand extends Command

  2. protected $signature = 'migrate:good_spec';

  3. public function handle(){

  4. DB::connect('old_旧数据库')->from('good_spec')->chunk(1000, function($data){

  5.   MigrateGoodSpecDataJob::dispatch($insertData)->onConnection('redis')->onQueue('migrate');

  1. class MigrateGoodSpecDataJob implements ShouldQueue

  2. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

  3. public function __construct($insertData)

  4. $this->insertData = $insertData;

  5. GoodSpecModel::insert($data);

  • 第三次,编辑可选数据范围命令,类似分页,可执行多个命令插入
  1. class MigrateGoodSpecCommand extends Command

  2. protected $signature = 'migrate:good_spec {limit}';

  3. public function handle(){

  4. $arr = $this->getBetweenId();

  5. if (empty($arr['min']) || empty($arr['max']))

  6. $this->log('数据超过范围:'.json_encode($arr));

  7. DB::connect('old_旧数据库')->from('good_spec')

  8. ->where('Id', '>=', $arr['min'])

  9. ->where('Id', '<', $arr['max'])

  10. ->chunk(1000, function($data){

  11. MigrateGoodSpecDataJob::dispatch($insertData)->onConnection('redis')->onQueue('migrate');

  12. public function getBetweenId()

  13. $str = $this->argument('limit');

  14. $arr = explode(',', $str);

  15. if (count($arr) !== 2){ throw \Exception('limit参数错误:'.$str); }

  16. $maxLimit = (int)$arr[0]+(int)$arr[1];

  17. $minId = DB::connect('old_旧数据库')->from('good_spec')->orderBy('Id', 'asc')->offset((int)$arr[0])->limit(1)->value('Id');

  18. $maxId = DB::connect('old_旧数据库')->from('good_spec')->orderBy('Id', 'asc')->offset($maxLimit)->limit(1)->value('Id');

  19. $maxId = GoodsModel::orderBy('Id', 'desc')->where('Id', '>', $minId)->value('Id');

     只能执行一次命令: 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内存,但是成本还是挺高。

本文由 黑白世界4648 第一时间收藏到GET,原文来自 → blog.csdn.net

「GetParty」

关注微信号,推送好文章

微信中长按图片即可关注

更多精选文章

评论
微博一键登入