Skip to content
adriancs edited this page Sep 15, 2026 · 4 revisions

MySqlBackup.NET.RawBytes — Backing Up and Restoring MySQL in C# Without Drivers or Object Conversions

For over a decade, MySqlBackup.NET (created in 2013) has provided a trusted solution for backing up and restoring MySQL databases in C#. Traditionally, it worked on top of an ADO.NET database driver:

  • MySql.Data (by Oracle)
  • MySqlConnector (MIT)
  • Devart dotConnect for MySQL

While these connectors are essential for building standard database-driven applications, using them for a database backup tool comes with massive, unnecessary baggage:

  1. The MySQL server sends well-formatted, text-protocol raw bytes across the TCP socket.
  2. The ADO.NET connector reads these bytes and allocates .NET objects (DateTime, string, decimal, boxed objects).
  3. The backup tool reads the DataReader, extracts the .NET objects, and calls .ToString() or formats them back into SQL strings.
  4. The strings are appended into a StringBuilder or written to disk.

From a backup engine's perspective, this conversion cycle: $$\text{Raw Wire Bytes} \longrightarrow \text{.NET Heap Objects} \longrightarrow \text{SQL String Dump}$$ is completely redundant. It wastes CPU cycles, puts massive pressure on the .NET Garbage Collector (GC), and slows down exports of large tables.


What Does MySQL Actually Send Over the Wire?

Consider a single table row with three simple columns:

Column Data Type Value Text Length
Col 1 VARCHAR Hello 5
Col 2 DATETIME 2026-07-01 10:00:00 19
Col 3 DECIMAL 99.95 5

In the MySQL Client/Server text protocol (COM_QUERY), incoming cell values are transmitted as length-encoded byte slices.

The stream looks like this conceptually:

(5)Hello(19)2026-07-01 10:00:00(5)99.95

If we inspect the wire bytes at the network level for "Hello" (length prefix 0x05), here is what travels across the TCP socket:

Binary:       00000101 01001000 01100101 01101100 01101100 01101111
Hexadecimal:  05       48       65       6C       6C       6F
ASCII:        5 (len)  H        e        l        l        o

Viewing all three columns in hexadecimal:

05 48 65 6C 6C 6F
5  H  e  l  l  o

13 32 30 32 36 2D 30 37 2D 30 31 20 31 30 3A 30 30 3A 30 30
19 2  0  2  6  -  0  7  -  0  1     1  0  :  0  0  :  0  0

05 39 39 2E 39 35
5  9  9  .  9  5

Notice something extraordinary? The wire bytes sent by MySQL are already perfectly formatted SQL text!

  • The date 2026-07-01 10:00:00 is already formatted ASCII.
  • The number 99.95 is already ASCII digits.

Converting 99.95 to a 128-bit .NET decimal structure only to turn around and call .ToString() back into the ASCII characters '9', '9', '.' '9', '5'` is completely wasteful work.


Short-Circuiting the Pipeline

Traditional backup libraries force every byte through the .NET type system:

MySQL Server
  └─ Wire Bytes
       └─ ADO.NET Driver (allocates & decodes into .NET Objects)
            └─ Backup Library (calls .ToString() on every cell)
                 └─ StringBuilder / String allocations (high GC pressure)
                      └─ Output Stream / File

MySqlBackup.NET.RawBytes short-circuits this entire path:

MySQL Server
  └── Wire Bytes (read directly into a reusable, connection-owned buffer)
        └── Output Stream (piped straight through with zero-allocation transformations)

By speaking the MySQL protocol directly:

  • Numeric Columns (INT, DECIMAL, DOUBLE, etc.): Raw wire bytes pass straight through to the stream without ever touching a managed numeric type.
  • Text Columns: Values are escaped byte-by-byte in-place within the incoming buffer (preserving UTF-8 / utf8mb4 multibyte sequences).
  • Binary Columns (BLOB, BIT, BINARY): Encoded directly into SQL hex literals (X'...') using a small, reusable scratch buffer.
  • Date & Time Columns: Zero dates (0000-00-00), microsecond precision (0–6 digits), and negative times pass through exactly as represented by the server, eliminating culture formatting and timezone drift bugs.

Nothing is boxed, nothing is materialized into DataTable objects, and memory usage scales strictly with the largest individual row/statement, not the size of your database.


Complete Feature Scope in Version 2

Unlike early prototypes that only supported base table exports, this modern engine provides enterprise-grade backup and restore capabilities:

  • Complete Schema Support: Backs up Tables, Views, Stored Procedures, Functions, Triggers, and Scheduled Events.
  • View Dependency Graph: Automatically resolves nested view dependencies via information_schema.VIEW_TABLE_USAGE so views restore in the correct topological order.
  • InnoDB Consistent Snapshots: Uses START TRANSACTION WITH CONSISTENT SNAPSHOT in REPEATABLE READ mode for non-blocking, transactionally consistent live backups.
  • Generated & Invisible Columns: Correctly includes invisible columns while automatically skipping computed VIRTUAL or STORED generated columns from INSERT projections.
  • TLS & Hardened Auth: Full TLS encryption support (OS certificate chain validation or optional SHA-256 certificate thumbprint pinning), supporting both mysql_native_password and caching_sha2_password (including RSA key exchange).
  • Stream-Centric Architecture: Export directly to any writable Stream (e.g. FileStream, GZipStream, network pipes) and restore from any readable Stream.
  • Zero Runtime Dependencies: Targets .NET Framework 4.8, .NET Standard 2.0, and .NET 8+ without any third-party NuGet packages.

Quick Start

1. Exporting a Database

using System;
using System.IO;
using MySqlBackup.RawBytes;

var connOptions = new ConnectionOptions
{
    Host = "127.0.0.1",
    Port = 3306,
    User = "root",
    Password = "your_password",
    Database = "app_database",
    UseTls = true // Enabled by default
};

using (var connection = MySqlConnection.Open(connOptions))
{
    var options = new BackupOptions
    {
        Consistency = BackupConsistency.InnoDbSnapshot, // Consistent repeatable-read snapshot
        MaxInsertBytes = 1024 * 1024,                   // 1 MB batched INSERTs
        Views = true,
        Routines = true,
        Triggers = true,
        Events = true
    };

    using (var file = File.Create("app_backup.sql"))
    {
        new BackupEngine(connection).Export("app_database", file, options);
    }
}

Console.WriteLine("Backup complete.");

2. Restoring a Database

The restore engine features an integrated, streaming SQL tokenizer (SqlScriptReader) that executes dumps statement-by-statement. It natively handles multi-character DELIMITER definitions, UTF-8 BOMs, conditional executable comments (/*!40101 ... */), and backslash escaping.

using System;
using System.IO;
using MySqlBackup.RawBytes;

var connOptions = new ConnectionOptions
{
    Host = "127.0.0.1",
    Port = 3306,
    User = "root",
    Password = "your_password",
    Database = "target_database"
};

using (var connection = MySqlConnection.Open(connOptions))
{
    var restoreOptions = new RestoreOptions
    {
        FailOnWarnings = true // Abort if a statement produces warnings (e.g., truncation)
    };

    using (var file = File.OpenRead("app_backup.sql"))
    {
        long executedStatements = new RestoreEngine(connection).Restore(file, restoreOptions);
        Console.WriteLine($"Restored {executedStatements} statements successfully.");
    }
}

Summary

MySqlBackup.NET.RawBytes returns to first principles: a backup tool's fundamental job is to move raw bytes between MySQL's network stream and storage. By bypassing the ADO.NET abstraction layer, you achieve lightning-fast backups, constant low memory usage, and zero driver dependency version conflicts.