N Noer

Importing a 58GB SQL Dump into MySQL 8 in Docker: Treat It Like a Recovery Runbook, Not a One-Line Command

Importing a 58GB SQL file into a MySQL 8 container looks like a simple mysql command until encoding, packet size, disk space, interrupted sessions, duplicate imports, and resource pressure turn it into an incident. The reliable path is to make the restore observable, resumable, and boring before trying to make it fast.

Importing a 58GB SQL file into MySQL 8 running inside a container is not really a “which command should I type?” problem. It is a small recovery exercise. The work becomes painful when the import runs for hours and then fails halfway through, silently mangles text because the character set was wrong, fills the data volume, leaves tables half-populated, or forces you to guess what state the database is in.

For large SQL imports, the first goal should not be maximum speed. The first goal is a restore path that is predictable, observable, and recoverable. Once that is in place, you can tune performance. If you reverse the order, a fast-looking command can easily become the slowest possible route because you end up rerunning it from scratch.

Do not fight a huge dump through an interactive source session

The MySQL client’s source command is convenient for small scripts and quick maintenance work. It is a poor default for a multi-hour restore. It runs through an interactive client session, produces noisy terminal output, makes logging and supervision awkward, and often gets used without an explicit character set. If your dump contains multilingual text and you import it with the wrong client encoding, the problem is no longer that the restore is slow; the problem is that the restored data may be wrong.

A safer baseline is a streaming import through the MySQL client, with the important assumptions stated in the command:

BASH
mysql -u root -p --default-character-set=utf8mb4 --binary-mode=1 --max_allowed_packet=1G target_db < /backup/big_dump.sql

This is not magic and it is not parallel loading. It simply avoids interactive overhead, behaves better under supervision, and makes the character set, binary handling, and packet limit explicit. Those three details prevent a surprising number of “the import worked, but the data is unusable” failures.

Confirm the recovery environment before the import

Before starting a 58GB restore, spend a few minutes checking the server and the container. The cheapest time to discover a bad character set, a low packet limit, a missing database, or a nearly full volume is before the import has been running all afternoon.

SQL
SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; SHOW VARIABLES LIKE 'max_allowed_packet'; SELECT @@sql_mode; SHOW DATABASES;

Then check the host side. In a Docker setup, the database process may be healthy while the host volume, memory limit, or bind mount is about to become the real bottleneck.

BASH
docker stats mysql8 docker inspect mysql8 --format '{{json .HostConfig.Memory}}' df -h du -h /backup/big_dump.sql

If the target volume cannot absorb the final data size plus temporary growth, stop. If the container has an unrealistically low memory limit, stop. If the target database was created with the wrong default charset, recreate it now. Fixing those problems after tens of gigabytes have already been loaded is not discipline; it is punishment.

Use temporary checks carefully, not blindly

Many import recipes recommend disabling foreign key checks, unique checks, and autocommit. The direction is reasonable, but those settings are not a universal accelerator and they do not repair bad data. They reduce validation work during a trusted restore. They are appropriate when the dump was produced from a consistent source and contains schema and data that are meant to be restored together.

BASH
mysql -u root -p target_db -e "SET GLOBAL max_allowed_packet=1073741824;"

Settings that must affect the import session should be part of the import session itself:

BASH
mysql -u root -p --default-character-set=utf8mb4 --binary-mode=1 --max_allowed_packet=1G --init-command="SET FOREIGN_KEY_CHECKS=0; SET UNIQUE_CHECKS=0; SET SQL_MODE='NO_ENGINE_SUBSTITUTION';" target_db < /backup/big_dump.sql

The boundary matters. Turning off checks can make sense for a known-good dump from your own environment. It is a bad idea for unknown data, mismatched schemas, or partial extracts. You are reducing import-time verification cost; you are not improving the quality of the source data.

Make the job observable

A restore that runs silently for six hours is operationally hostile. You need to know whether bytes are still flowing, whether MySQL is accepting statements, whether the container is under memory pressure, and whether disk growth looks sane. If pv is available, put it between the dump and the client:

BASH
pv /backup/big_dump.sql | mysql -u root -p --default-character-set=utf8mb4 --binary-mode=1 --max_allowed_packet=1G target_db

Keep a second terminal for lightweight checks:

BASH
docker stats mysql8 mysqladmin -u root -p processlist mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Threads_running';"

The goal is not to stare at the process every second. The goal is to have enough signals to distinguish a slow but healthy import from a dead session, a blocked statement, or a host-level resource problem.

Plan for interruption before it happens

Large SQL dumps are often not naturally resumable. A plain dump may contain CREATE TABLE, INSERT, and index operations in a sequence that assumes a clean target. If the import dies halfway through, rerunning the same file may produce duplicate rows, “table already exists” errors, or a mixture of old and new data.

Before importing, decide what your rollback method is. In a containerized environment, the cleanest answer is usually one of these:

  • restore into a fresh database name and swap application configuration after validation;
  • snapshot or copy the MySQL data volume before the import;
  • drop and recreate the target database if the import fails;
  • split the dump by schema or table when the source format allows it.

Do not discover your recovery strategy during the failure. A big import is already stressful; a half-restored production-like database with no rollback point is much worse.

Validate the restored database, not just the exit code

A zero exit code is necessary, but it is not a full validation. After the import, check table counts, important constraints, application-level smoke paths, and character rendering for fields that commonly break. At minimum, record the schema count and row counts for critical tables:

SQL
SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables WHERE table_schema = 'target_db' GROUP BY table_schema; SELECT COUNT(*) FROM target_db.important_table;

If the dump came from another environment, compare representative counts against the source export notes. If the application depends on full-text indexes, generated columns, triggers, events, or routines, verify those too. SQL imports often appear complete while non-table objects were skipped because of privileges, definer issues, or dump options.

Keep heavy restores away from everyday workloads

It is tempting to run an import wherever Docker is already available. That is fine for a disposable development restore, but a 58GB import can saturate disk I/O, inflate buffer pool churn, and make unrelated services miserable. Environments for AI tools, local agents, design workspaces, and database recovery drills should not be casually mixed with production workloads on the same host.

The practical pattern is simple: restore into an isolated container or host, give it enough disk and memory, log the command, validate the result, and only then promote the data or point applications at it. Treat the restore as an operation with a runbook. The command is only one line; the reliability comes from everything around it.