Transfer Database

Trong quá trình phát triển và bảo trì dự án, nhiệm vụ di chuyển cơ sở dữ liệu từ hệ thống này sang hệ thống khác là một nhiệm vụ khá là phổ biến.

Chúng ta thường sẽ sử dụng câu truy vấn sau trong việc xuất dữ liệu sang định dạng CSV:

SELECT
   ...
INTO OUTFILE '...'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM ...

Tuy nhiên, khi thực hiện câu truy vấn trên từ AWS RDS , chúng ta thường gặp phải vấn đề đó là không có quyền truy cập vào server trong việc thực hiện xuất dữ liệu sang định dạng CSV.

Để giải quyết vấn đề này, chúng ta sẽ sử dụng một phương pháp thay thế để có thể xuất dữ liệu sang định dạng CSV, đó là thực hiện thông qua lệnh mysql và chuyển hướng đầu ra của dữ liệu thành CSV:

mysql -u username -p --database=dbname --host=rdshostname --port=rdsport --batch -e "select * from yourtable" | sed 's/\t/","/g;s/^/"/;s/$/"/;s/\n//g' > yourlocalfilename

Dựa vào phương pháp thay thế trên, chúng ta sẽ tích hợp nó vào command của Laravel để thực hiện việc di chuyển cơ sở dữ liệu đơn giản hơn.

Command này chúng ta sẽ đặt cho nó một cái tên là TransferMySQLToMySQL sẽ hỗ trợ chúng ta trong việc di chuyển dữ liệu giữa các cơ sở dữ liệu MySQL trong Laravel trở nên nhanh chóng và hiệu quả hơn bao giờ hết, tiết kiệm thời gian và công sức trong quá trình quản lý dữ liệu.

Thực hiện TransferMySQLToMySQL trong Laravel

Đầu tiên, bạn sẽ tạo một command mới bằng cách sử dụng Artisan CLI của Laravel. Mở Terminal và chạy lệnh sau:

php artisan make:command TransferMySQLToMySQL

Mở tập tin app/Console/Commands/TransferMySQLToMySQL.php. Bạn sẽ thấy một class cơ bản đã được tạo sẵn. Bây giờ chúng ta sẽ cập nhật nội dung xử lý như sau:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

/**
 * Suggests an alternative approach as well via exporting your data in CSV format by selecting the data in the MySQL command line client and piping the output to reformat the data as CSV, like so:
 * mysql -u username -p --database=dbname --host=rdshostname --port=rdsport --batch -e "select * from yourtable" | sed 's/\t/","/g;s/^/"/;s/$/"/;s/\n//g' > yourlocalfilename
 *
 * Supposedly simpler approach, if you know and specify the fields upfront:
 * mysql -uroot -ppassword --database=dbtest-e "select concat(field1,',',field2,',',field3) FROM tabletest" > tabletest.csv
 *
 * I recently encountered some larger and more complex outputs where "sed" was not enough and decided to come up with a simple utility to do exactly that.
 * I build a module called sql2csv that can parse the output of the MySQL CLI:
 * mysql my_db -e "SELECT * FROM some_mysql_table" | sql2csv
 * sql2csv -u root -p "secret" -d my_db --query "SELECT * FROM some_mysql_table;"
 *
 * To quote strings (recommended), you'll need to do a bit of extra work in your query:
 * SELECT id, CONCAT('"', REPLACE(text_column, '"', '""'), '"'), float_column FROM my_table
 *
 * The REPLACE escapes any double-quote characters in the text_column values. I would also suggest using iso8601 strings for datetime fields, so:
 * SELECT CONCAT('"', DATE_FORMAT(datetime_column, '%Y%m%dT%T'), '"') FROM my_table
 *
 * https://stackoverflow.com/questions/9536224/exporting-a-table-from-amazon-rds-into-a-csv-file
 */
class TransferMySQLToMySQL extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'transfer:mysql-to-mysql';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Transfers data from one MySQL database to another using CSV export and import';

    /**
     * Source system configuration details.
     *
     */
    protected $source_system_host     = '';
    protected $source_system_port     = '';
    protected $source_system_username = '';
    protected $source_system_password = '';

    /**
     * Destination system configuration details.
     *
     */
    protected $destination_system_host     = '';
    protected $destination_system_port     = '';
    protected $destination_system_username = '';
    protected $destination_system_password = '';
    protected $destination_system_database = '';

    /**
     * Table and query configuration.
     *
     */
    protected $table          = '';
    protected $columns        = '';
    protected $local_infile   = '';
    protected $local_infiles  = [];
    protected $query          = '';
    protected $lines_per_file = 0;

    /**
     * Execute the console command.
     */
    public function handle()
    {
        // Measure the start time of the entire process
        $start_time           = microtime(true);
        $start_time_formatted = now()->format('Y-m-d H:i:s');
        // Configure source system connection parameters
        $this->setupSourceSystem();
        // Configure destination system connection parameters
        $this->setupDestinationSystem();
        // Configure export settings
        $this->configureExportSettings();
        // Export data from the old system to a CSV file
        $this->exportToCSV();
        // Split the exported CSV file into smaller parts.
        $this->splitCSVIntoParts();
        // Import data from a CSV file to the new system
        $this->importFromCSV();
        // Deletes the CSV import files.
        $this->deleteCSVImport();
        // Measure the end time of the entire process
        $end_time           = microtime(true);
        $end_time_formatted = now()->format('Y-m-d H:i:s');
        $execution_time     = round($end_time - $start_time, 1);
        print("Execution time: {$start_time_formatted} - {$end_time_formatted} ({$execution_time} seconds)\n");
    }

    /**
     * Configure the source system connection parameters.
     */
    protected function setupSourceSystem()
    {
        $this->source_system_host     = '';
        $this->source_system_port     = '';
        $this->source_system_username = '';
        $this->source_system_password = '';
    }

    /**
     * Configure the destination system connection parameters.
     */
    protected function setupDestinationSystem()
    {
        $this->destination_system_host     = '';
        $this->destination_system_port     = '';
        $this->destination_system_username = '';
        $this->destination_system_password = '';
        $this->destination_system_database = '';
    }

    /**
     * Configure export settings.
     */
    protected function configureExportSettings()
    {
        $this->table        = 'table';
        $this->columns      = ['column'];
        $this->local_infile = storage_path('transfers/table.csv');
        $this->query        = "SELECT * FROM table";
    }

    /**
     * Export data from the old system to a CSV file.
     */
    protected function exportToCSV()
    {
        $command = sprintf(
        "mysql -h %s -P %s -u %s -p%s --batch --quick -e \"%s\" | sql2csv > %s",
            $this->source_system_host,
            $this->source_system_port,
            $this->source_system_username,
            $this->source_system_password,
            $this->query,
            $this->local_infile
        );
        exec($command, $output, $status);

        $command = "awk 'NR > 1 { print t } { t = \$0 } END { ORS = \"\"; print t }' $this->local_infile > temp.csv && mv temp.csv $this->local_infile";
        exec($command, $output, $status);

        return $status;
    }

    /**
     * Import data from a CSV file to the new system.
     */
    protected function importFromCSV()
    {
        $columns = implode(',', array_map(function($col) {
            return '\\`' . $col . '\\`';
        }, $this->columns));

        $ignore_lines = "IGNORE 1 LINES";

        foreach ($this->local_infiles as $local_infile) {
            $command = sprintf(
            "mysql -h %s -P %s -u %s -p%s -D %s --local-infile -e \"LOAD DATA LOCAL INFILE %s INTO TABLE %s FIELDS TERMINATED BY ',' ENCLOSED BY '\\\"' LINES TERMINATED BY '\\n' %s (%s);\"",
                $this->destination_system_host,
                $this->destination_system_port,
                $this->destination_system_username,
                $this->destination_system_password,
                $this->destination_system_database,
                escapeshellarg($local_infile),
                $this->table,
                $ignore_lines,
                $columns
            );

            exec($command, $output, $status);
        }
    }

    /**
     * Splits the CSV file into multiple parts based on the specified number of lines per file.
     */
    protected function splitCSVIntoParts()
    {
        if ($this->lines_per_file < 1) {
            $this->local_infiles = [$this->local_infile];
            return 0;
        }
        $script  = base_path('split_csv.sh');
        $command = "$script $this->local_infile $this->lines_per_file";
        exec($command, $output, $status);

        $this->local_infiles = $output;

        return $status;
    }

    /**
     * Deletes the main CSV file and its parts from storage.
     */
    protected function deleteCSVImport()
    {
        if ($this->lines_per_file > 1) {
            unlink($this->local_infile);
        }
        foreach ($this->local_infiles as $local_infile) {
            unlink($local_infile);
        }
    }
}

Khi bạn xem xét kỹ hơn, bạn sẽ thấy chúng ta đã thêm hàm splitCSVIntoParts để thực hiện nhiệm vụ chia nhỏ dữ liệu CSV.

Đối với các tệp CSV quá lớn, thì trong quá trình truyền tải và xử lý chúng ta có thể gặp nhiều khó khăn.

Việc chia nhỏ dữ liệu CSV giúp việc truyền tải và xử lý trở nên dễ dàng hơn, đồng thời giảm thiểu nguy cơ gặp lỗi do kích thước tập tin quá lớn.

Bên dưới là nội dung của tập tin split_csv.sh, bạn hãy tạo split_csv.sh trong thư mục Root của dự án:

#!/bin/bash

# Check if the CSV file path is provided
if [ -z "$1" ]; then
    echo "Please provide the path to the CSV file."
    exit 1
fi

# Read input parameters
file="$1"
num_splits="${2:-100}" # Default to 100 if the second parameter is not provided
path=$(dirname "$file")
output_prefix=$(basename "$file" .csv)

# Change to the directory containing the CSV file
cd "$path" || { echo "Cannot change to directory $path"; exit 1; }

# Execute the mlr command with the specified parameters
mlr --csv --from "$file" split -n "$num_splits" --prefix "${output_prefix}_part"

# Print the absolute paths of the created part files
find "$PWD" -name "${output_prefix}_part_*" -print | sed -E 's/.*_part_([0-9]+)\.csv/\1 \0/' | sort -k1,1n | cut -d' ' -f2-

Dockerfile

Nếu dự án của bạn đang sử dụng Docker, bạn cũng có thể tham khảo Dockerfile bên dưới để tích hợp với lệnh TransferMySQLToMySQL, giúp việc triển khai và quản lý trở nên dễ dàng hơn.

Dưới đây là Dockerfile được cài đặt thêm thư viện sql2csv, hỗ trợ phân tích cú pháp đầu ra của MySQL CLI, rất hữu ích khi làm việc với dữ liệu lớn và phức tạp.

# Use the latest Ubuntu base image
FROM ubuntu:24.10

# Update and install necessary packages
RUN apt-get update -y \
    && apt upgrade -y \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y \
        miller \
        mysql-server \
        php8.3 \
        php8.3-cli \
        php8.3-mysql \
        php8.3-xml \
        php8.3-bcmath \
        php8.3-curl \
        php8.3-common \
        php8.3-mbstring \
        php8.3-tokenizer \
        php8.3-zip \
        curl \
        git \
        unzip \
        python3 \
        python3-pip \
        python3-venv  # Install python3-venv for creating virtual environments

# Install PHP-FPM and enable configuration
RUN apt install -y php8.3-fpm && a2enconf php8.3-fpm

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Create a directory for the virtual environment and install sql2csv inside it
RUN python3 -m venv /opt/venv
RUN /opt/venv/bin/pip install sql2csv

# Expose the default port for MySQL
EXPOSE 3306

# Add CMD command to start MySQL, activate the virtual environment, and run sql2csv
CMD ["bash", "-c", "service mysql start && source /opt/venv/bin/activate && sql2csv --help && tail -f /dev/null"]

Ngoài ra, chúng ta cũng có thể có thể di chuyển dữ liệu sang Amazon S3 bằng Amazon DMS từ bất kỳ nguồn cơ sở dữ liệu nào được hỗ trợ.

Khi sử dụng Amazon S3 làm mục tiêu trong tác vụ Amazon DMS, dữ liệu sẽ được ghi theo định dạng phân tách bằng dấu "," (.csv) theo mặc định.

Để lưu trữ nhỏ gọn hơn và có tùy chọn truy vấn nhanh hơn, bạn cũng có tùy chọn ghi dữ liệu vào định dạng Apache Parquet (.parquet).

Using Amazon S3 as a target for AWS Database Migration Service

CÓ THỂ BẠN QUAN TÂM

Simplify Your Laravel Workflow with Laravel Pint

Simplify Your Laravel Workflow with Laravel Pint

Laravel Pint là gì? Laravel Pint là một công cụ sửa đổi mã nguồn của bạn để mã nguồn của bạn tuân thủ theo các tiêu chuẩn. Nói một cách khác, Laravel Pint sẽ quét toàn bộ mã nguồn của bạn, phát...

Laravel 9 REST API With Sanctum Authentication

Laravel 9 REST API With Sanctum Authentication

Laravel Sanctum Laravel Sanctum cung cấp một hệ thống authentication đơn giản cho các SPA, ứng dụng Mobile và API đơn giản sử dụng token. Sanctum cho phép ứng dụng của bạn phát hành các mã token...

Method WhereAny / WhereAll  in Laravel Eloquent

Method WhereAny / WhereAll in Laravel Eloquent

New Laravel 10: Eloquent WhereAny() và WhereAll() Laravel cung cấp cho chúng ta khả năng xây dựng các truy vấn dữ liệu mạnh mẽ với Eloquent ORM, giúp chúng ta có thể xử lý các truy vấn cơ sở dữ li...

Laravel Socialite Login With Github

Laravel Socialite Login With Github

GitHub GitHub là một nền tảng phát triển phần mềm trực tuyến. Nó được sử dụng để lưu trữ, theo dõi và cộng tác trong các dự án phần mềm. Nó giúp các nhà phát triển dễ dàng chia sẻ các tập tin mã...

Laravel Validation

Laravel Validation

Lợi thế lớn nhất của Laravel so với các Framework khác là Laravel tích hợp rất nhiếu tính năng được tích hợp sẵn. Trong bài viết này, chúng ta sẽ tìm hiểu về Laravel Validation. Chức năng Là một...

Laravel TinyMCE 6 Image Upload

Laravel TinyMCE 6 Image Upload

TinyMCE TinyMCE là một trình soạn thảo  WYSIWYG  được xây dựng trên nền tảng Javascript, được phát triển dưới dạng mã nguồn mở theo giấy phép  MIT  bởi Tiny Technologies Inc. TinyMCE cho phép ngư...

Laravel Socialite Login With Facebook

Laravel Socialite Login With Facebook

Ngoài xác thực dựa trên biểu mẫu điển hình, Laravel cũng cung cấp một cách đơn giản và thuận tiện để sử dụng Laravel Socialite để xác thực với các nhà cung cấp OAuth. Socialite hiện hỗ trợ xác thực qu...

Encrypted HTTP Live Streaming with Laravel FFMpeg

Encrypted HTTP Live Streaming with Laravel FFMpeg

HTTP Live Streaming (HLS)  HTTP Live Streaming (HLS) là một trong những giao thức phát trực tuyến video được sử dụng rộng rãi nhất . Mặc dù nó được gọi là HTTP "live" streaming, nhưng nó được sử dụn...

Laravel Custom Request

Laravel Custom Request

Nếu bạn có một form để người dùng nhập dữ liệu và bạn muốn kiểm tra dữ liệu đầu vào trước khi lưu xuống database chẳng hạn thì bạn có 2 cách sau đây: Cách 1: Bạn thêm validate trực tiếp vào hàm sto...

ManhDanBlogs