1. 程式人生 > >合併不同伺服器的資料表(thinkphp5+mysql)

合併不同伺服器的資料表(thinkphp5+mysql)

本認第一次寫部落格,可能有些地方表達不清晰,望大家見諒;

最近業務有個需求,需要將不同渠道的會員資訊合併在一起,然後插入一張表中(每個渠道的會員資料在不同伺服器,這點是操蛋的)

自己先百度了一哈,網上好像沒有此類的文章,所以自己決定把這次的經驗分享出來;

先說哈自己的整體思路:

  1. 先把其它伺服器的資料庫表複製對映到本地
  2. 合併成一張表
  3. 迴圈合併的資料插入到本地資料庫表中並處理自己業務的邏輯

下面圖文講解

  •  首先需要把mysql的FEDERATED引擎開啟,預設是關閉的,可以通過show engines 命令檢視;

在mysql配置檔案加上紅色標註的

準備工作已ok,下面貼上程式碼

  • 1.配置資訊

  •  2.把其它伺服器資料表對映到本地
 public function syncMemberTable()
    {
        // thinking :不同伺服器的資料表 通過fea引擎 把其它伺服器的表 對映到本地資料庫 在把所有的平臺的member資料 合併成 在迴圈寫入更新資料

        $merge_sql = 'create table ' . $this->member_table_merge . ' as   ';

        //連結池
        foreach ($this->dsns as $key => $dsn) {
            $channel_table = $this->member_table . '_' . $key;
            // 建立對映關係
            $show_create_sql = ' show create table ' . $this->member_table;

            $create_table_sql_result = Db::connect($dsn)->query($show_create_sql);
            $create_table_sql = null;
            $create_table_sql = $create_table_sql_result[0]['Create Table'];


            // 把 table 名和engine 修改。並增加 connect  語句
            $fed_channel_table = $channel_table . '_FED';

            //本地是否已經存在

            $fed_channel_table_has_sql = ' show tables like ' . '\'' . $fed_channel_table . '\'';//show tables like 後面表名比需要引號

            $fed_channel_table_has_result = Db::execute($fed_channel_table_has_sql);//有 1 沒有 0
            if ($fed_channel_table_has_result == 0) {
                Log::record("原來的sql:" . $create_table_sql, 'INFO');
                $new_create_fed_sql = '';
                $new_create_fed_sql = str_replace($this->member_table, $fed_channel_table, $create_table_sql);
                $new_create_fed_sql = str_replace("InnoDB", 'FEDERATED  ', $new_create_fed_sql);
                $user = $dsn['username'];
                $password = $dsn['password'];
                $host = $dsn['hostname'];
                $database = $dsn['database'];
                $connection = " CONNECTION='mysql://$user:
[email protected]
$host:3306/$database/$this->member_table'"; $new_create_fed_sql = $new_create_fed_sql . $connection; Log::record("替換FED的sql:" . $new_create_fed_sql, 'INFO'); $new_create_result = Db::execute($new_create_fed_sql);//執行對映sql if ($new_create_result !== false) { $msg = "$key 渠道的$this->member_table" . '表 對映到本地資料庫成功'; echo $msg; Log::record($msg, 'INFO'); } else { $msg = "$key 渠道的$this->member_table" . '表 對映到本地資料庫失敗'; echo $msg; Log::record($msg, 'INFO'); exit();//出錯不執行 } } //對映成功後 在本地在新建一張表 因為對映的表不能操作表 //先檢視是否存在 $new_create_table_has_sql = ' show tables like ' . '\'' . $channel_table . '\''; $result = Db::execute($new_create_table_has_sql);//存在1 不存在0 if ($result == 0) { /*--------------------------------------------------*/ Log::record('---------------starting create table ------------------', 'debug'); $new_create_sql = ''; //$new_create_sql = str_replace($this->member_table, $channel_table, $create_table_sql); $new_create_sql = ' create table ' . $channel_table . ' as select * from ' . $fed_channel_table; Log::record("create的sql:" . $new_create_sql, 'INFO'); $result = Db::execute($new_create_sql);//執行建表 if ($result != false) { $msg = "$key 渠道的$this->member_table" . '表 copy到本地資料庫成功'; echo $msg; Log::record($msg, 'INFO'); } else { $msg = "$key 渠道的$this->member_table" . '表 copy到本地資料庫失敗,Error:' . $result; echo $msg; Log::record($msg, 'INFO'); exit(); } } //判斷欄位是否存在 $fields = Db::getTableInfo($channel_table, 'fields'); if (!in_array('channel_id', $fields)) { //複製成功後新增渠道欄位 後面做邏輯處理 渠道欄位直接放在id後面 $channel_id = $dsn['channel_id']; $channel_sql = " ALTER TABLE `$channel_table` ADD COLUMN `channel_id` int(10) UNSIGNED NOT NULL DEFAULT $channel_id AFTER `id`"; $channel_result = Db::execute($channel_sql); if ($channel_result != false) { $msg = "$key 渠道的$this->member_table" . '表 的渠道ID欄位新增成功'; echo $msg; Log::record($msg, 'INFO'); //新增成功後可以刪除多餘的對映表 Db::execute('DROP TABLE ' . $fed_channel_table); } else { $msg = "$key 渠道的$this->member_table" . '表 的渠道ID欄位新增失敗'; echo $msg; Log::record($msg, 'INFO'); exit(); } } $keys = array_keys($this->dsns); $merge_sql .= ' select distinct * from ' . $channel_table . ($key != end($keys) ? ' union all ' : ''); } Log::record($merge_sql, 'INFO'); //不渠道的表合併成 $result = Db::execute($merge_sql); if ($result == false) { echo $msg = $this->member_table . '合併表建立失敗!'; Log::record($msg, 'INFO'); } else { echo $msg = $this->member_table . '合併表建立成功!'; Log::record($msg, 'INFO'); } }

上面大概的思路是通過show create table 把需要合併的表的建立mysql 獲取,然後把引擎改成FEDERATED,然後在本地再次建立一份檢視或者表,因為我這裡需要新增一個channel_id欄位到其它伺服器對映到本地的表中,但是FEDERATED引擎不能操作表,所以需要在多一步步驟;

表建立過後,然後通過mysql 的 union 或者 union all 合併成一張表,到此其它伺服器的表在本地資料庫已合併在一起,有資料了接下來就好辦了; 

效果:

某個伺服器的表

合併過後的表

  •  3.同步資料
//同步會員表資料
    public function SynMemberTableData()
    {
        //把會員插入的資訊另寫一個日誌
        Log::init([
            'type' => 'File',
            'path' => LOG_PATH . 'MemberLog' . DS,
        ]);
        $ts = microtime(true);
        Log::record("-----------------start寫入會員資訊---------執行時間-----------" . date("H:i:s"), 'INFO');
        Debug::remark('begin');


        //統計一共有多少使用者

        $total = Db::table($this->member_table_merge)->group('mobile,channel_id')->count();

        Log::record('一共有' . $total . '個使用者', 'INFO');

        if (!empty($total)) {
            //程序處理資料條數
            $limit = 10000;
            $pages = ceil($total / $limit);

            for ($i = 1; $i <= $pages; $i++) {
                $members = Db::table($this->member_table_merge)->group('mobile,channel_id')->order('channel_id')->page($i, $limit)->select();
                //多程序
                foreach ($members as $k => $member) {
                    //同個渠道的人是否已經有了
                    $has = $this->member_model->where(['mobile' => $member['mobile'], 'channel_id' => $member['channel_id']])->count();

                    if (empty($has)) {
                        unset($member['id']);
                        $insert_id = $this->member_model->insertGetId($member);
                        if ($insert_id) {
                            $msg = '使用者:' . $member['username'] . '[channel_id=' . $member['channel_id'] . ']資料插入成功,插入ID為:' . $insert_id;
                            echo $msg;
                            Log::record($msg, 'INFO');
                        } else {
                            $msg = '使用者:' . $member['username'] . '[channel_id=' . $member['channel_id'] . ']資料插入失敗';
                            echo $msg;
                            Log::record($msg, 'Error');
                            continue;
                        }
                    } else {
                        continue;
                    }

                    unset($member[$k]);//unset()
                }


//
//                $reserveProcess = new \Swoole\Process(function ($worker) use ($i, $limit, $member_model, $member_dis_model) {
//
//
//
//                }, false);
                Debug::remark('end');
                Log::record("-----------------會員資訊寫入end-------------結束時間-------" . date("H:i:s"), 'INFO');
                Log::record('寫入耗時' . (microtime(true) - $ts) . 's ', 'debug');
                Log::record('所耗記憶體' . Debug::getRangeMem('begin', 'end') . 'kb', 'debug');
                Log::save();
                echo "end SynMemberTableData task..." . (microtime(true) - $ts) . "s " . "\r\n";
            }


        }

        $this->SynMemberTableData();//


    }

插入到本地資料庫的表

到此大致上已完成了自己的業務需求了;

希望本文能夠對你有些幫助