---
title: "Chuyển MySQL sang PostgreSQL"
description: "Chuyển bản dump MySQL hoặc MariaDB thành tập lệnh PostgreSQL nạp được ngay lần đầu, kèm báo cáo mọi thay đổi. Trong trình duyệt: bản dump không được tải lên."
canonical: https://intosql.com/vi/mysql-sang-postgresql
last_updated: 2026-10-15
---

# Chuyển MySQL sang PostgreSQL

Thả bản dump từ mysqldump, mariadb-dump, mysqlpump, phpMyAdmin hoặc Adminer, dạng .sql hoặc .sql.gz, và tải xuống tập lệnh PostgreSQL với cùng bảng, khóa, chỉ mục và dữ liệu. Mọi giá trị PostgreSQL không chứa được nguyên trạng, và mọi đối tượng nó không hỗ trợ, đều được liệt kê trong báo cáo.

## Những gì thay đổi từ MySQL sang PostgreSQL

Mỗi hàng là một quy tắc trình chuyển đổi áp dụng, kèm dòng báo cáo được ghi khi quy tắc thay đổi dữ liệu của bạn.

| Trong MySQL | Trong PostgreSQL | Dòng báo cáo |
| --- | --- | --- |
| TINYINT(1), BOOL | boolean khi mọi giá trị là 0 hoặc 1, nếu không thì smallint | `bool_kept_numeric` |
| UNSIGNED integers | kiểu số nguyên rộng hơn kế tiếp kèm CHECK không âm; BIGINT UNSIGNED là bigint khi mọi giá trị vừa, nếu không thì numeric(20,0) | `unsigned_widened` |
| ZEROFILL and display widths | bị bỏ | `zerofill_dropped` |
| FLOAT(M,D) | real hoặc double precision, bỏ số chữ số thập phân cố định | `float_scale_dropped` |
| BIT(1), BIT(n) | boolean, bit(n) |  |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULL | `invalid_date_null` |
| NOT NULL columns with values that become NULL | bỏ NOT NULL | `not_null_relaxed` |
| DEFAULT '0000-00-00' | không có giá trị mặc định | `zero_date_default` |
| TIME | time khi mọi giá trị nằm trong một ngày, nếu không thì interval | `time_as_interval` |
| YEAR | smallint kèm CHECK phạm vi |  |
| CHAR(n) | varchar(n) |  |
| DATETIME | timestamp |  |
| TIMESTAMP | timestamptz theo múi giờ bản dump nêu, UTC khi không nêu | `timestamp_zone_assumed` |
| ENUM | text kèm CHECK các giá trị |  |
| ENUM holding '' (MySQL's error value) | giá trị rỗng được ràng buộc kiểm tra cho phép | `enum_invalid_value` |
| ENUM or SET holding values outside its list | không có CHECK | `enum_check_skipped` |
| SET | text[] kèm CHECK rằng mọi phần tử có trong danh sách |  |
| JSON | jsonb; json khi một giá trị chứa \u0000 | `json_not_jsonb` |
| NUL bytes in text | bị xóa | `nul_removed` |
| text that isn't valid in its character set | byte lỗi thành ký tự thay thế | `invalid_utf8_replaced` |
| text decoded twice (Ã±) | sao chép nguyên trạng | `mojibake_suspected` |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY, bộ đếm đặt sau các id đã sao chép |  |
| MyISAM AUTO_INCREMENT on the second column of a key | một bộ đếm cho cả bảng | `myisam_group_autoincrement` |
| ON UPDATE CURRENT_TIMESTAMP | một trình kích hoạt BEFORE UPDATE | `on_update_trigger` |
| generated columns | STORED | `virtual_as_stored` |
| generated columns with functions PostgreSQL lacks | một cột thường | `generated_not_translated` |
| INVISIBLE columns | cột thường | `invisible_column` |
| CHECK constraints | được chuyển đổi, viết trực tiếp trong CREATE TABLE |  |
| CHECK constraints with functions PostgreSQL lacks | không được tạo | `check_skipped` |
| CHECK ... NOT ENFORCED | không được tạo | `check_not_enforced` |
| defaults with functions PostgreSQL lacks | không có giá trị mặc định | `default_not_translated` |
| keys and indexes on a prefix, c(20) | chỉ mục trên left(c, 20) | `prefix_unique` |
| FULLTEXT indexes | GIN trên to_tsvector('simple', ...) | `fulltext_as_gin` |
| indexes on values over 2,000 bytes | hash | `index_on_wide_values` |
| SPATIAL indexes | không được tạo khi không có PostGIS | `spatial_index_skipped` |
| functional indexes PostgreSQL can't write | không được tạo | `index_skipped` |
| foreign keys | sau dữ liệu, NOT VALID, rồi xác thực kèm cảnh báo về hàng mồ côi |  |
| foreign keys to columns without a unique key | không được tạo | `foreign_key_skipped` |
| table and column names | chữ thường khi tên vẫn duy nhất | `identifier_renamed` |
| names over 63 bytes | rút ngắn kèm mã băm | `name_shortened` |
| GEOMETRY, POINT, POLYGON... | WKB trong bytea | `geometry_as_wkb` |
| INET4, INET6, UUID (MariaDB) | inet, uuid |  |
| BINARY, VARBINARY, BLOB | bytea |  |
| PARTITION BY | một bảng | `partitions_dropped` |
| WITH SYSTEM VERSIONING (MariaDB) | bảng chứa các hàng hiện tại | `system_versioning_dropped` |
| MyISAM, MEMORY and other engines | bảng thường | `engine_dropped` |
| views, triggers, procedures, functions and events | được liệt kê trong báo cáo | `not_converted_objects` |
| dumps without CREATE TABLE | kiểu suy ra từ giá trị | `no_schema` |
| geometries in a dump without CREATE TABLE | WKB trong bytea | `geometry_inferred` |
| _ci collations | so sánh chính xác | `case_insensitive_collation` |
| SET NAMES utf8 from utf8mb4 tables | được chuyển đổi, kèm cảnh báo | `dump_lost_four_byte_chars` |
| dumps without SET NAMES whose text isn't UTF-8 | đọc dưới dạng Latin-1 (Windows-1252) | `charset_guessed` |
| sjis, cp932, gbk, big5 dumps | đọc dưới dạng UTF-8, kèm cảnh báo | `unsupported_charset` |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | được tính ra giá trị |  |
| other expressions in rows | NULL | `expression_value` |
| DEFAULT in VALUES | giá trị mặc định của cột | `default_keyword_filled` |
| DEFAULT in VALUES without a literal default | NULL | `default_keyword_null` |
| REPLACE and INSERT IGNORE | hàng thường | `replace_as_insert` |
| types IntoSQL doesn't know | text | `unknown_type` |
| values that don't fit their column | NULL | `invalid_value_null` |
| unreadable geometries | NULL | `bad_geometry` |
| CREATE SEQUENCE (MariaDB) | CREATE SEQUENCE kèm giá trị hiện tại |  |

## Nạp tập lệnh vào PostgreSQL

Tập lệnh tạo bảng, sao chép các hàng, rồi thêm khóa, chỉ mục và khóa ngoại, nên nạp nhanh và dừng ở lỗi đầu tiên.

```shell
psql -v ON_ERROR_STOP=1 -d mydb -f mysql.postgresql.sql
```

Khóa ngoại được thêm dưới dạng NOT VALID rồi được xác thực. Nếu một hàng trỏ tới hàng không tồn tại, tập lệnh cho bạn biết ràng buộc nào, thay vì thất bại giữa chừng.

Với bản dump lớn, hãy tạo cơ sở dữ liệu trước và chạy tập lệnh bằng psql trên cùng máy với máy chủ.

## Tạo bản dump MySQL để có kết quả tốt nhất

Trình chuyển đổi đọc những gì bản dump chứa. Các tùy chọn sau giữ lại nhiều dữ liệu hơn trong đó:

- --default-character-set=utf8mb4 giữ emoji và các ký tự 4 byte khác, những ký tự mà bản dump utf8 làm mất trước khi trình chuyển đổi thấy.
- --hex-blob ghi cột nhị phân dưới dạng thập lục phân, không bị hỏng khi sao chép giữa các hệ thống.
- --routines --triggers --events thêm các đối tượng đó vào bản dump, để báo cáo có thể liệt kê chúng.

## Những gì không được chuyển đổi

Khung nhìn, trình kích hoạt, thủ tục lưu trữ, hàm và sự kiện được viết bằng ngôn ngữ riêng của từng cơ sở dữ liệu. Chúng được liệt kê theo tên trong báo cáo, không bao giờ bị bỏ âm thầm, để bạn viết lại cho PostgreSQL.

## Đo đối chiếu với pgloader

Cùng các bản dump, được IntoSQL và pgloader 3.6.7 chuyển đổi và nạp trên cùng một máy (i9-10900K 10 nhân với SATA SSD), vào tháng 10 năm 2026.

- Cột TINYINT(1) chứa 2, -1 và -5: pgloader nạp true cho từng giá trị, không có cảnh báo. IntoSQL giữ các số và báo cáo giải thích lý do.
- Bản dump có giá trị mặc định 'it's', bảng có tên chứa dấu backtick, ENUM chứa giá trị rỗng và TIME trên 24 giờ: pgloader dừng lại. IntoSQL chuyển đổi và nạp được.
- Dữ liệu pgloader nạp đã mất khóa chính, khóa duy nhất, khóa ngoại và identity. IntoSQL giữ tất cả.
- Bản dump 5.3 GB với 30 triệu hàng: pgloader cần khôi phục nó vào một MySQL đang chạy trước, mất 18 phút, rồi hết bộ nhớ với cấu hình mặc định. IntoSQL chuyển đổi bản dump trong 10 giây, và psql nạp tập lệnh trong khoảng 3 phút.
- Trên 476 bản dump từ MySQL 5.5 đến 9 và MariaDB 10.3 đến 12, nạp vào PostgreSQL 13 và 18: không có lỗi nạp, và không giá trị nào thay đổi mà không có dòng trong báo cáo.

## Câu hỏi về MySQL sang PostgreSQL

### Bản dump của tôi có bị tải lên không?

Không. Trình chuyển đổi chạy trong trình duyệt và đọc bản dump từ ổ đĩa theo từng phần, nên nó không bao giờ rời khỏi máy tính của bạn. Ứng dụng máy tính và dòng lệnh cũng chuyển đổi ngay trên máy tính của bạn.

### Bản dump có thể lớn đến đâu?

Trình chuyển đổi không bao giờ giữ toàn bộ bản dump trong bộ nhớ: nó đọc tệp hai lần, theo từng đoạn, và ghi tập lệnh trong lúc đọc. Bản dump nhiều gigabyte vẫn chuyển đổi được trong trình duyệt, trong ứng dụng máy tính và bằng dòng lệnh.

### Nó có đọc được bản dump MariaDB không?

Có. Bản dump từ mariadb-dump và từ MariaDB 10.3 đến 12 được chuyển đổi theo cùng cách, kể cả các cột INET6, INET4 và UUID của MariaDB.

### Ngày 0000-00-00 sẽ ra sao?

PostgreSQL không có ngày bằng 0, nên chúng trở thành NULL và cột chấp nhận NULL. Báo cáo cho biết số lượng trong mỗi cột.

### Giá trị AUTO_INCREMENT có được giữ không?

Có. Mỗi cột AUTO_INCREMENT trở thành cột identity, mọi hàng giữ id của nó, và id tiếp theo tiếp tục sau id lớn nhất, hoặc sau AUTO_INCREMENT của bảng nếu giá trị đó lớn hơn.

### TINYINT(1) có thành boolean không?

Chỉ khi mọi giá trị trong cột là 0 hoặc 1. Nếu cột chứa số khác, như 2 hoặc -1, nó vẫn là số và báo cáo giải thích lý do, nên không giá trị nào vô tình thành true.

### Còn thủ tục lưu trữ và trình kích hoạt thì sao?

Chúng được liệt kê theo tên trong báo cáo và không được chuyển đổi, vì mỗi cơ sở dữ liệu viết chúng bằng ngôn ngữ riêng. ON UPDATE CURRENT_TIMESTAMP là ngoại lệ: nó trở thành một trình kích hoạt PostgreSQL.

### Tập lệnh nạp được trên những phiên bản PostgreSQL nào?

PostgreSQL 13 đến 18. Cùng một tập lệnh nạp được trên từng phiên bản, và dừng ở lỗi đầu tiên nếu có sự cố.

## Các chuyển đổi khác sang PostgreSQL

- [Chuyển Excel sang PostgreSQL](https://intosql.com/vi/excel-sang-postgresql)
- [Chuyển CSV sang PostgreSQL](https://intosql.com/vi/csv-sang-postgresql)
- [Chuyển JSON sang PostgreSQL](https://intosql.com/vi/json-sang-postgresql)
