You are moving a MySQL 8.0 or 8.4 database to a new server, a new major version, Amazon RDS, Aurora, or MySQL HeatWave, and a physical copy is not an option. The two parallel logical tools are MySQL Shell's dump and load utilities and the community mydumper/myloader pair, and they fail in different places. mydumper stops on an RDS source with We support LOCK_ALL and SAFE_NO_LOCK modes for RDS/Aurora. MySQL Shell stops a HeatWave-bound dump with Compatibility issues with MySQL HeatWave Service ... were found. This guide compares MySQL Shell 26.10 with mydumper 1.0.19 on locking, chunking, managed targets, deferred indexes, resume, and GTIDs, with commands for both.
- Current releases: MySQL Shell 26.10.0 (20 October 2026) and mydumper v1.0.19-1 (5 November 2026). Shell is now one release line that supports MySQL Server 8.4, 9.7, and 26.7.
- On RDS and Aurora sources, set mydumper's lock mode to
LOCK_ALLorSAFE_NO_LOCK. Shell falls back toLOCK TABLESby itself. - Shell has built-in managed-target checks (
ocimds,compatibility,targetVersion), native S3, OCI, and Azure output, and a resumable load. mydumper has none of these built in. - By default, mydumper skips triggers, routines, and events, and myloader loads with binary logging off. Shell includes those objects and logs by default.
- Neither tool sets GTIDs on the target by default. Use
updateGtidSetormyloader --set-gtid-purgedbefore replicating to the target for cutover.
Scope and review date: November 2026. This post covers MySQL Shell 26.10 and mydumper/myloader 1.0.19-1 with MySQL 8.0, 8.4, 9.7, and 26.7 sources and self-managed, RDS, Aurora, and HeatWave targets. Options and defaults were checked on 9 November 2026 against the MySQL Shell 26.10 manual and release notes and the mydumper documentation and v1.0.19-1 source. The commands were not run in a lab, so rehearse them on a copy first.
Which MySQL Shell and mydumper versions should you use in November 2026?
The MySQL Shell 26.7.0 release notes (28 July 2026) moved Shell to a single release line, discontinued the 8.4 and 9.7 Shell series, and state that the latest Shell supports MySQL Server 8.4, 9.7, and 26.7. 26.7.0 also fixed unbalanced chunking of tables with composite primary keys, a Percona contribution. 26.10.0 followed on 20 October 2026.
mydumper's first stable long-term support release, v1.0.1-1, shipped on 17 May 2026, and the GitHub releases page lists v1.0.19-1 (5 November 2026) as the latest. Its README says it is a community project, not a Percona, MariaDB, or MySQL product.
mysqlsh --version
mydumper --version
myloader --versionInterpretation: load a Shell dump with the same or a newer Shell. Each dump lists the features it uses, and an older loader stops with an error that names the Shell version it needs. For mydumper, run the same release on both sides.
How do MySQL Shell dump/load and mydumper/myloader compare?
Shell writes DDL as .sql and data as chunked, tab-separated files with JSON metadata, and loads them with LOAD DATA LOCAL INFILE. mydumper writes SQL INSERT files by default (--format also offers LOAD_DATA, CSV, and CLICKHOUSE) plus a plain-text metadata file. Neither loader is documented to read the other tool's dumps.
MySQL Shell 26.10 and mydumper 1.0.19 side by side
Switch tools to see the defaults you inherit before you set any option
MySQL Shell 26.10
| Area | MySQL Shell |
|---|---|
| Default threads | 4 for dump and 4 for load |
| Chunking | On; bytesPerChunk default 64 MB |
| Compression | On; zstd level 1 |
| Triggers, routines, events | Included by default |
| Binary logging during load | On (skipBinlog: false) |
| Secondary indexes during load | Full-text deferred; others built with the table |
| Object storage | Native: OCI, S3-compatible, Azure Blob |
| Resume after failure | Progress file; partly loaded tables deduplicated |
mydumper 1.0.19
| Area | mydumper and myloader |
|---|---|
| Default threads | 4, minimum 2; 0 means one per CPU |
| Chunking | By rows (--rows, adaptive) or MB (--chunk-filesize) |
| Compression | Off unless you pass --compress (gzip or zstd) |
| Triggers, routines, events | Skipped unless you pass -G, -R, -E |
| Binary logging during load | Off (myloader sets SQL_LOG_BIN=0) |
| Secondary indexes during load | All added after each table loads |
| Object storage | None built in; --stream to a CLI |
| Resume after failure | resume file, written on a graceful stop only |
Most common surprise: with default myloader settings, the target's replicas and binary-log-based recovery never see the loaded rows. Use --enable-binlog if they must; 1.0.0 marked it as discouraged in favor of a [myloader_session_variables] section in the defaults file.
How does each tool take a consistent snapshot?
Both tools briefly block writes with a global lock, open START TRANSACTION WITH CONSISTENT SNAPSHOT on every worker, then release the lock. They differ in which lock they take and what happens when they cannot take it.
With the default consistent: true, the Shell dump utilities issue FLUSH TABLES WITH READ LOCK if the account has RELOAD, otherwise LOCK TABLES. Once every thread has its snapshot, Shell takes LOCK INSTANCE FOR BACKUP, which allows DML but blocks DDL that would break the dump, and releases the read lock. Without BACKUP_ADMIN, Shell runs an extra consistency check; if it fails, an instance dump stops. Shell documents consistency only for InnoDB tables.
-- Source account for util.dumpInstance(), per the Shell 26.10 manual
CREATE USER 'dumper'@'10.0.%' IDENTIFIED BY 'change-me';
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, RELOAD ON *.* TO 'dumper'@'10.0.%';
GRANT BACKUP_ADMIN, REPLICATION CLIENT ON *.* TO 'dumper'@'10.0.%';
-- Dumping users (the default) also needs SELECT on the mysql schemaInterpretation: REPLICATION CLIENT lets Shell record the binary log position; without it the dump has no coordinates. Since Shell 8.0.27, a read lock refused with access denied falls back to LOCK TABLES. That matters on RDS: the AWS knowledge center states that RDS does not support a global read lock.
util.dumpInstance() with the default consistent: true
Illustrative output, shaped like MySQL Shell's messages; schema and table counts are placeholders
Watch the first lines: writes are blocked from "Global read lock acquired" until "Global read lock has been released". A long-running query on the source holds up the read lock, so check the process list before you start.
mydumper picks its method with --sync-thread-lock-mode. In the v1.0.19-1 source, the default AUTO gives MySQL and Percona Server 8.x LOCK INSTANCE FOR BACKUP plus FLUSH TABLES WITH READ LOCK, and other MySQL major versions, such as 9.7 and 26.7, the read lock alone. On RDS and Aurora, AUTO stops with the error quoted above. LOCK_ALL locks every exported table and needs LOCK TABLES. SAFE_NO_LOCK takes no lock, and the lock modes page says it stops if the binary log position moved while threads synced.
# RDS or Aurora source: pick the lock mode explicitly
mydumper --defaults-extra-file=/etc/mydumper/source.cnf \
--database=appdb --outputdir=/backups/appdb-2026-11 \
--sync-thread-lock-mode=LOCK_ALL \
--triggers --events --routinesInterpretation: on a busy primary the position keeps moving, so SAFE_NO_LOCK is likely to abort. To avoid locking an RDS primary, dump from a read replica with replication stopped, as AWS recommends for mysqldump.
How do threads, chunking, and compression work in each tool?
Shell splits each table into files of about bytesPerChunk (64 MB before compression) using table statistics, falls back to row counts for tables without a primary or unique key, and chunks partitions independently. Each thread has its own connection, and maxRate caps reads per thread.
// mysqlsh dumper@db-01:3306 --js
// threads and bytesPerChunk are starting points; set final values from a rehearsal
util.dumpInstance("/backups/app-2026-11", {
threads: 8,
bytesPerChunk: "128M",
compression: "zstd;level=3",
maxRate: "50M",
checksum: true
})Interpretation: checksum: true writes @.checksums.json, which util.loadDump(..., {checksum: true}) verifies after each table or chunk loads. zstd accepts levels 1 to 22 and gzip 0 to 9; both default to 1.
mydumper chunks by rows. --rows accepts MIN:START_AT:MAX, and its help text says it doubles a chunk that takes under 1 second and halves one over 2 seconds. --chunk-filesize splits by MB instead, and string primary keys split only with --split-string-pk.
mydumper --defaults-extra-file=/etc/mydumper/source.cnf \
--database=appdb --outputdir=/backups/appdb-2026-11 \
--threads=8 --rows=100000 --compress=zstd \
--triggers --events --routines --checksum-allInterpretation: name the algorithm in --compress: the help text says a bare flag means gzip, but the code picks zstd when it is installed. myloader --checksum=fail stops on a mismatch; the default is warn. Neither project publishes a head-to-head benchmark, so time both tools on a production-sized copy.
Can you dump straight to S3, OCI, or Azure object storage?
Shell can. Pass osBucketName and osNamespace for OCI, s3BucketName (with s3Region, s3Profile, or s3EndpointOverride) for S3-compatible stores, or azureContainerName for Azure. The output path becomes an object prefix, and util.loadDump() takes the same options.
util.dumpInstance("app-2026-11", {s3BucketName: "example-dumps", s3Region: "us-east-1", threads: 8})
// mysqlsh admin@target:3306 --js
util.loadDump("app-2026-11", {s3BucketName: "example-dumps", s3Region: "us-east-1", threads: 8,
progressFile: "/var/tmp/app-2026-11-progress.json"})Interpretation: 26.10.0 fixed uploads to S3 buckets with Object Lock retention, which could fail earlier because checksum headers were not sent. To skip intermediate storage, the copy utilities stream from source to target directly.
mydumper has no object storage client. --stream writes each finished file to standard output, and myloader --stream reads them from standard input, so pipe through a CLI such as aws s3 cp -.
mydumper --defaults-extra-file=/etc/mydumper/source.cnf \
--database=appdb --outputdir=/backups/stage \
--threads=8 --compress=zstd --triggers --events --routines --stream \
| aws s3 cp - s3://example-dumps/appdb-2026-11.stream --expected-size 600000000000
aws s3 cp s3://example-dumps/appdb-2026-11.stream - \
| myloader --defaults-extra-file=/etc/mydumper/target.cnf \
--directory=/restore/stage --threads=8 --streamInterpretation: the AWS CLI reference requires --expected-size in bytes for streams over 50 GB, so set it from your own size estimate. A failed single-object upload starts again from zero.
How do you prepare a dump for HeatWave, RDS, or Aurora?
For MySQL HeatWave, set ocimds: true. Shell then comments out DATA DIRECTORY, INDEX DIRECTORY, and ENCRYPTION clauses, and flags non-InnoDB tables, restricted grants, and missing primary keys, which HeatWave high availability requires. A dry run lists every issue without writing a dump.
util.dumpInstance("/backups/app-2026-11", {dryRun: true, ocimds: true, targetVersion: "8.4.7"})
util.dumpInstance("/backups/app-2026-11", {
ocimds: true, targetVersion: "8.4.7",
compatibility: ["force_innodb", "strip_restricted_grants", "strip_tablespaces",
"skip_invalid_accounts", "create_invisible_pks"]
})Interpretation: set targetVersion to the target's exact version; without it, Shell assumes its own. create_invisible_pks makes the loader add a my_row_id invisible primary key and needs MySQL 8.0.24 or later on the target. strip_definers is not needed for HeatWave 8.2.0 and later.
RDS and Aurora have no flag of their own, but compatibility works without ocimds, as the manual's S3 example shows. Set local_infile to 1 in the RDS parameter group, because the loader uses LOAD DATA LOCAL INFILE. Shell skips users by default (loadUsers: false), so create application accounts yourself.
# mydumper: drop definers at load time
myloader --defaults-extra-file=/etc/mydumper/target.cnf \
--directory=/backups/appdb-2026-11 --threads=8 \
--skip-definerInterpretation: --replace-definer sets a definer that exists on the target instead. Otherwise the mydumper privileges page lists SET_USER_ID and SYSTEM_USER, and the Shell manual notes that MySQL 8.4 replaced SET_USER_ID with SET_ANY_DEFINER. mydumper has no managed-target checks, so run Shell's dry run against the source even when mydumper moves the data.
How do you load with deferred secondary indexes?
Inserting into a table that already has every secondary index updates each index row by row. Adding the indexes after the rows lets InnoDB build them in bulk, and the Shell manual says deferring "can reduce loading times". The defaults differ.
Parallel chunked dump, then a load with deferred indexes
Rows move in parallel chunks; secondary indexes are added after a table's last chunk lands
Plan for step 3: a load that shows all rows loaded can still be adding indexes to the largest tables. Leave time for it before replication catch-up.
Shell's deferTableIndexes takes off, fulltext (the default), or all, which creates only the primary key and auto-increment indexes during the load. Shell adds a table's indexes at the same time using the server's parallel index build, so innodb_ddl_threads on the load sessions applies.
// mysqlsh admin@target:3306 --js
util.loadDump("/backups/app-2026-11", {
threads: 8,
deferTableIndexes: "all",
sessionInitSql: ["SET SESSION innodb_ddl_threads = 8"],
analyzeTables: "on",
checksum: true
})Interpretation: analyzeTables: "on" runs ANALYZE TABLE after each table loads, so statistics are fresh at cutover. To build indexes later on purpose, load with loadIndexes: false, then rerun with loadIndexes: true and deferTableIndexes: "all".
myloader's --optimize-keys defaults to AFTER_IMPORT_PER_TABLE: it strips the keys from each CREATE TABLE and adds them with ALTER TABLE once that table's chunks are in. Foreign keys go in a later stage. AFTER_IMPORT_ALL_TABLES waits for every table, and SKIP turns it off.
myloader --defaults-extra-file=/etc/mydumper/target.cnf \
--directory=/backups/appdb-2026-11 --threads=8 \
--optimize-keys=AFTER_IMPORT_PER_TABLE \
--max-threads-for-index-creation=4 --checksum=failInterpretation: --max-threads-for-index-creation (default 4) limits concurrent index builds, and --optimize-keys-batchsize caps indexes per ALTER TABLE.
What happens if a load fails halfway?
Shell records completed steps in a progress file, load-progress.<server_uuid>.json in the dump directory by default. Rerun the same util.loadDump() command and it skips finished work; the load utility manual says it deduplicates partly loaded tables itself.
// Resume: same URL and options as the interrupted run
util.loadDump("/backups/app-2026-11", {threads: 8, deferTableIndexes: "all"})
// Start over only after dropping everything the partial load created
util.loadDump("/backups/app-2026-11", {threads: 8, resetProgress: true})Interpretation: never edit dump files between failure and resume; the manual calls that undefined behavior. If the load stops with MySQL Error 1197 on max_binlog_cache_size, set maxBytesPerTransaction to that value or lower and resume.
myloader writes a resume file only on a graceful stop: a confirmed Ctrl+C or SIGTERM. Restart with --resume; without the flag, myloader stops with Resume file found but --resume has not been provided.
myloader --defaults-extra-file=/etc/mydumper/target.cnf \
--directory=/backups/appdb-2026-11 --threads=8 --resumeInterpretation: a process killed outright (out of memory, SIGKILL, host loss) leaves no resume file, and the default --drop-table=FAIL stops on the existing tables. Drop the target schema and reload.
How do you carry GTIDs across for replication?
For a short cutover you load the dump, then replicate from the source until the target catches up. The target needs the source's GTID set at the snapshot recorded as purged. Shell stores it as gtidExecuted in @.json, and showMetadata: true prints it with the binary log position.
// Self-managed target with gtid_mode=ON
util.loadDump("/backups/app-2026-11", {threads: 8, skipBinlog: true, updateGtidSet: "append"})CHANGE REPLICATION SOURCE TO
SOURCE_HOST = 'db-01.internal', SOURCE_PORT = 3306,
SOURCE_USER = 'repl', SOURCE_PASSWORD = 'change-me',
SOURCE_SSL = 1, SOURCE_AUTO_POSITION = 1;
START REPLICA;Interpretation: append requires that the dumped set does not overlap the target's gtid_executed, and it is not for table dumps or Group Replication targets. On HeatWave, run SET @@GLOBAL.gtid_purged = "+<set>" after the load. On RDS, use mysql.rds_set_external_source_gtid_purged and the auto-position procedure from the RDS stored procedure reference, checking the versions each supports.
mydumper writes File, Position, and Executed_Gtid_Set into metadata. Because myloader already loads with binary logging off, --set-gtid-purged can run SET GLOBAL gtid_purged from that file after the import.
grep -E 'File|Position|Executed_Gtid_Set' /backups/appdb-2026-11/metadata
myloader --defaults-extra-file=/etc/mydumper/target.cnf \
--directory=/backups/appdb-2026-11 --threads=8 --set-gtid-purgedInterpretation: for a source without GTIDs, use the file and position; Shell's manual points to ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS, available from MySQL 8.0.23.
How do you migrate between MySQL versions with dump and load?
A logical dump lets you skip versions or change platforms because the target rebuilds every table. The risk moves to the DDL: reserved words, removed functions, and authentication plugins. Shell's upgrade checker finds those on the source, whichever tool copies the data. 26.10.0 added a removedLegacyHashingFunctions check for MD5(), SHA1(), and SHA() in generated columns, functional indexes, CHECK constraints, and defaults, which must change before moving to MySQL 9.6 or later.
util.checkForServerUpgrade("admin@db-01:3306", {targetVersion: "8.4.7", outputFormat: "TEXT"})Interpretation: targetVersion accepts any release from 8.0.11 up to the Shell's own version. The loader refuses a dump whose source major version is not consecutive with the target's; set ignoreVersion: true only after the checker comes back clean.
Migration runbook: dump, load, replicate, cut over
Eight steps with MySQL Shell; step 7 is the outage window
-
Check the source against the target version
Fix every reported error on the source first.
util.checkForServerUpgrade("admin@db-01:3306", {targetVersion: "8.4.7"}) -
Dry-run the dump
Lists compatibility issues and writes nothing.
util.dumpInstance("/backups/app", {dryRun: true, ocimds: true}) -
Prepare the target
Turn on
local_infileand GTIDs; create application and replication accounts.SET PERSIST local_infile = 1; -
Dump the source
Writes pause only until every thread has its snapshot.
util.dumpInstance("/backups/app", {threads: 8, checksum: true}) -
Load with deferred indexes and the GTID set
Rerun the same command to resume after a failure.
util.loadDump("/backups/app", {deferTableIndexes: "all", skipBinlog: true, updateGtidSet: "append", checksum: true}) -
Replicate until lag is zero
On RDS, use the stored procedures instead.
CHANGE REPLICATION SOURCE TO SOURCE_HOST = 'db-01', SOURCE_AUTO_POSITION = 1; START REPLICA; -
Stop writes and cut over
OutageMake the source read-only, wait until the target has applied every source GTID, then repoint the application.
SET GLOBAL super_read_only = ON; -
Verify and keep the source
Compare row counts and checksums, then stop replication. Keep the old server until you no longer need a way back.
STOP REPLICA; RESET REPLICA ALL;
Rollback point: until step 7 the source takes all writes. After the application writes to the target, going back means replicating those writes in reverse or losing them, so set up reverse replication before cutover if you need that option.
Frequently asked questions
Is mydumper faster than MySQL Shell dump and load?
There is no general answer, and neither project publishes a head-to-head benchmark. Both use parallel threads and chunked files, and both can defer secondary indexes. Speed depends on table sizes, primary key types, row width, storage, network, and target settings such as binary logging. Rehearse both tools on a production-sized copy and time the full dump, transfer, load, and verification.
Can MySQL Shell util.loadDump() load a mydumper dump?
No. util.loadDump() is documented for dumps created by MySQL Shell's instance, schema, and table dump utilities, which use JSON metadata and tab-separated data files. myloader reads mydumper's own SQL or LOAD DATA files and its metadata file. Use the same tool at both ends of a migration, with matched versions.
Does mydumper work with Amazon RDS and Aurora MySQL?
Yes, but not in its default lock mode. mydumper 1.0.19 stops on RDS and Aurora unless you set --sync-thread-lock-mode to LOCK_ALL or SAFE_NO_LOCK, because RDS does not support a global read lock. LOCK_ALL needs the LOCK TABLES privilege. SAFE_NO_LOCK aborts if the binary log position moves, so it suits a read replica with replication stopped.
Can I resume a MySQL Shell load after it fails?
Yes. util.loadDump() records completed steps in a progress state file, stored in the dump directory by default. Run the same command again and it skips finished work and handles partly loaded tables. Do not change the dump files in between. Use resetProgress only after dropping every object the partial load created on the target.
Key takeaways
- Use MySQL Shell 26.10.0 or mydumper 1.0.19-1, and load with the same or a newer release than the one that dumped.
- Pass
--triggers --events --routinesto mydumper and decide on binary logging for myloader; Shell includes those objects and logs by default. - On RDS and Aurora sources, set mydumper's lock mode to
LOCK_ALLorSAFE_NO_LOCK, or dump from a stopped read replica. - Run Shell's
checkForServerUpgradeand anocimdsorcompatibilitydry run before any cross-version or managed-target load, whichever tool moves the data. - Apply the dump's GTID set with
updateGtidSetor--set-gtid-purged, then replicate to close the gap before cutover.
Plan a MySQL logical migration with JusDB
JusDB's MySQL migration team can review your source for version and managed-target compatibility and help you choose between MySQL Shell and mydumper for your tables and network. We can also write and rehearse a dump, load, and replication runbook with a tested rollback point.
Bring your source and target versions, data size and largest tables, and the outage window you can accept. Your team approves every production change.