MySQL
Tested with: MySQL 8.4.6 on Databasezy (Percona Operator for MySQL) · zb CLI 0.1
MySQL is the relational engine behind a large share of web applications and frameworks. Databasezy runs it on the Percona Operator for MySQL with TLS on every connection.
Overview
Section titled “Overview”Instances start with one primary. On paid plans you can add a standby for high availability (--ha) and read
replicas (--replicas). 8.4 is the default for new instances; choose 8.0 only when an application needs it.
| Status | Available |
|---|---|
| Category | Relational |
| Versions | 8.4, 8.0 (newest is the default) |
| Protocol and port | MySQL wire protocol on 3306 |
| Runtime | Operator-backed (Percona Operator for MySQL) |
| Backups | mysqldump (logical dump) |
| Point-in-time recovery | No |
| Pause | Scale to zero |
| Free plan | Yes (size f0) |
| Features | Read replicas, High availability (standby) |
| Licence | GPL-2.0 |
Create an instance
Section titled “Create an instance”- Open the portal and choose New instance.
- Pick MySQL and a version (8.4, 8.0).
- Choose a size and region. On the Free plan the size is f0. Secure placement is listed only after your organization has signed the BAA.
- Check the hourly price and monthly estimate, then confirm. The Connect tab fills in when the instance is ready.
zb instances create --engine mysql --engine-version 8.4 \ --size s1 --region us-east --name mysql-demo --wait# On the Free plan, use --size f0
# Reveal the credentials once and store them in your secret managerzb instances credentials reveal mysql-democurl -sS https://api.databasezy.com/v1/orgs/$ZB_ORG/projects/$ZB_PROJECT/instances \ -H "Authorization: Bearer $ZB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "engine": "mysql", "engine_version": "8.4", "size": "s1", "region": "us-east", "name": "mysql-demo"}'
The response is 201 with the instance in status requested, or 202 when a
team approval policy applies. See the REST API reference.
Connect
Section titled “Connect”Every snippet uses ssl-mode=VERIFY_IDENTITY or the driver’s equivalent, so both the certificate chain and the
host name are checked.
MySQL listens on port 3306 (MySQL wire protocol). Versions: 8.4, 8.0. Replace the example host with the one on your instance's Connect tab; credentials are shown once at creation.
mysql://app:<password>@mysql-7f3k.us-east.databasezy.com:3306/app?ssl-mode=VERIFY_IDENTITY&ssl-ca=databasezy-ca.pemimport { readFileSync } from "node:fs";import mysql from "mysql2/promise";
export const pool = mysql.createPool({ host: "mysql-7f3k.us-east.databasezy.com", port: 3306, user: "app", password: process.env.ZB_PASSWORD, database: "app", ssl: { ca: readFileSync("databasezy-ca.pem", "utf8"), rejectUnauthorized: true, // verify chain and hostname }, connectionLimit: 10,});
const [rows] = await pool.query("select version() as v");console.log(rows[0].v);Tested with: MySQL 8.4 · mysql2 3.11
import osimport pymysql
conn = pymysql.connect( host="mysql-7f3k.us-east.databasezy.com", port=3306, user="app", password=os.environ["ZB_PASSWORD"], database="app", ssl={"ca": "databasezy-ca.pem"}, ssl_verify_cert=True, ssl_verify_identity=True, # hostname check)with conn.cursor() as cur: cur.execute("select version()") print(cur.fetchone())Tested with: MySQL 8.4 · PyMySQL 1.1
package main
import ( "crypto/tls" "crypto/x509" "database/sql" "fmt" "os"
"github.com/go-sql-driver/mysql")
func main() { pem, err := os.ReadFile("databasezy-ca.pem") if err != nil { panic(err) } roots := x509.NewCertPool() roots.AppendCertsFromPEM(pem) mysql.RegisterTLSConfig("databasezy", &tls.Config{ RootCAs: roots, ServerName: "mysql-7f3k.us-east.databasezy.com", // hostname verification MinVersion: tls.VersionTLS12, })
dsn := fmt.Sprintf("app:%s@tcp(mysql-7f3k.us-east.databasezy.com:3306)/app?tls=databasezy&parseTime=true", os.Getenv("ZB_PASSWORD")) db, err := sql.Open("mysql", dsn) if err != nil { panic(err) } db.SetMaxOpenConns(10) var v string if err := db.QueryRow("select version()").Scan(&v); err != nil { panic(err) } fmt.Println(v)}Tested with: MySQL 8.4 · go-sql-driver/mysql 1.8
use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions, MySqlSslMode};
#[tokio::main]async fn main() -> Result<(), sqlx::Error> { let opts = MySqlConnectOptions::new() .host("mysql-7f3k.us-east.databasezy.com") .port(3306) .database("app") .username("app") .password(&std::env::var("ZB_PASSWORD").expect("ZB_PASSWORD")) .ssl_mode(MySqlSslMode::VerifyIdentity) .ssl_ca("databasezy-ca.pem");
let pool = MySqlPoolOptions::new().max_connections(10).connect_with(opts).await?; let (v,): (String,) = sqlx::query_as("select version()").fetch_one(&pool).await?; println!("{v}"); Ok(())}Tested with: MySQL 8.4 · sqlx 0.8 (mysql, tls-rustls)
// One-time: import the CA into a PKCS12 truststore// keytool -importcert -noprompt -alias databasezy -file databasezy-ca.pem \// -keystore databasezy-truststore.p12 -storetype PKCS12 -storepass changeitimport com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;
public final class Db { public static HikariDataSource open() { var cfg = new HikariConfig(); cfg.setJdbcUrl("jdbc:mysql://mysql-7f3k.us-east.databasezy.com:3306/app" + "?sslMode=VERIFY_IDENTITY" + "&trustCertificateKeyStoreUrl=file:databasezy-truststore.p12" + "&trustCertificateKeyStoreType=PKCS12" + "&trustCertificateKeyStorePassword=changeit"); cfg.setUsername("app"); cfg.setPassword(System.getenv("ZB_PASSWORD")); cfg.setMaximumPoolSize(10); return new HikariDataSource(cfg); }}Tested with: MySQL 8.4 · MySQL Connector/J 9.1 · HikariCP 6.2
using MySqlConnector;
var cs = new MySqlConnectionStringBuilder{ Server = "mysql-7f3k.us-east.databasezy.com", Port = 3306, Database = "app", UserID = "app", Password = Environment.GetEnvironmentVariable("ZB_PASSWORD"), SslMode = MySqlSslMode.VerifyFull, SslCa = "databasezy-ca.pem", MaximumPoolSize = 10,};await using var conn = new MySqlConnection(cs.ConnectionString);await conn.OpenAsync();await using var cmd = new MySqlCommand("select version()", conn);Console.WriteLine(await cmd.ExecuteScalarAsync());Tested with: MySQL 8.4 · MySqlConnector 2.4
<?php$pdo = new PDO("mysql:host=mysql-7f3k.us-east.databasezy.com;port=3306;dbname=app;charset=utf8mb4", "app", getenv("ZB_PASSWORD"), [ PDO::MYSQL_ATTR_SSL_CA => "databasezy-ca.pem", PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,]);echo $pdo->query("select version()")->fetchColumn();Tested with: MySQL 8.4 · PHP 8.3 · PDO mysql (mysqlnd)
require "mysql2"
client = Mysql2::Client.new( host: "mysql-7f3k.us-east.databasezy.com", port: 3306, username: "app", password: ENV.fetch("ZB_PASSWORD"), database: "app", sslca: "databasezy-ca.pem", sslverify: true, # verify chain and hostname ssl_mode: :verify_identity)puts client.query("select version() as v").first["v"]Tested with: MySQL 8.4 · mysql2 0.5 · Ruby 3.3
mysql --host mysql-7f3k.us-east.databasezy.com --port 3306 --user app --password \ --ssl-mode=VERIFY_IDENTITY --ssl-ca=databasezy-ca.pem --tls-sni-servername=mysql-7f3k.us-east.databasezy.com app
# orzb connect mysql-7f3kTested with: MySQL 8.4 · mysql 8.4 client
The MySQL connection guide covers TLS, pooling and the allow-list in more depth. Framework guides: Laravel, Rails, Django, Prisma, Drizzle, Spring, SQLAlchemy.
See Connecting to Databasezy for host names, the CA bundle and the allow-list, which work the same for every engine.
Migrate in
Section titled “Migrate in”You can bring an existing MySQL database in from these sources. Each links to a step-by-step guide.
| Source | How | Continuous sync | Guide status |
|---|---|---|---|
| Amazon RDS and Aurora | Connection string | Yes | Coming soon · phase 2 |
| Railway | Connection string | Yes | Coming soon · phase 2 |
| Aiven | Connection string | Yes | Coming soon · phase 2 |
| DigitalOcean Managed Databases | Connection string | Yes | Coming soon · phase 2 |
| Google Cloud SQL | Connection string | Yes | Coming soon · phase 3 |
| Azure Database | Connection string | Yes | Coming soon · phase 3 |
| PlanetScale | Connection string | No | Available |
| Self-hosted server | Connection string, Local tools (zb migrate --from local) | Yes | Available |
| Local files and dumps | File upload, Local tools (zb migrate --from local) | No | Available |
| Docker container | Local tools (zb migrate --from local) | No | Available |
| Another Databasezy instance | Instance to instance | Yes | Coming soon · phase 2 |
MySQL sources can use continuous sync through GTID-based binlog replication when the source allows an outside replica (PlanetScale does not). Moving to MariaDB later is covered in engine conversions.
How migrations work explains preflight, verification and cutover.
Backups and restore
Section titled “Backups and restore”Backups are consistent logical dumps (mysqldump --single-transaction) taken on your plan’s schedule, and a backup
restores into a new instance. The binary log is not archived yet, so point-in-time recovery is not offered for MySQL;
restore to the most recent backup instead.
MySQL backups use mysqldump (logical dump). They run inside the instance's namespace, stream straight to the cell's object storage and are checksummed on upload. How often they run and how long they are kept follows your plan's backup policy. A backup is always taken before a resize or a version upgrade.
Point-in-time recovery is not available for MySQL. A restore returns the data as of a scheduled or manual backup. Restores create a new instance by default and leave the original untouched; an in-place restore asks you to type the instance name and takes a pre-change backup first.
zb backups create mysql-demo --label before-release # manual snapshotzb backups list mysql-demozb backups restore mysql-demo <backup-id> --name mysql-demo-restorePause and scale to zero
Section titled “Pause and scale to zero”MySQL can scale to zero. A paused instance has no running pods and bills no compute; its storage and backups are kept and billed as usual. Connections are refused until you resume it. On the Free plan an instance pauses by itself after 15 minutes without connections and wakes on the next one; the gateway holds that connection for up to 30 seconds while it starts.
zb instances pause mysql-demozb instances resume mysql-demoSee Pause and resume for schedules, wake times and billing while paused.
Limits and sizes
Section titled “Limits and sizes”MySQL runs on every size, including the Free plan's f0. The size sets the CPU, memory, storage ceiling and connection limit; the gateway refuses connections over the limit with a protocol error. Storage grows in steps up to the ceiling, and you can resize at any time.
| Size | vCPU | Memory | Max storage | Max connections | ≈ $ / month |
|---|---|---|---|---|---|
f0 | 0.063 | 512 MiB | 1 GB | 20 | Free |
s0 | 0.25 | 1 GiB | 20 GB | 60 | $10 |
s1 | 0.5 | 2 GiB | 50 GB | 100 | $15 |
s2 | 1 | 4 GiB | 200 GB | 200 | $60 |
m2 | 2 | 8 GiB | 500 GB | 400 | $110 |
m4 | 4 | 16 GiB | 1 TB | 800 | $210 |
l8 | 8 | 32 GiB | 4 TB | 1,500 | $410 |
l16 | 16 | 64 GiB | 8 TB | 3,000 | $960 |
xl32 | 32 | 128 GiB | 16 TB | 5,000 | $1,870 |
Full details, including hourly prices and burst CPU, are in the size catalogue; plan quotas are in limits and quotas.
Security
Section titled “Security”The app user has every privilege on the app database and none outside it, so it cannot create other databases or
users. After a credential rotation the previous password keeps working for 10 minutes, then MySQL discards it. Clients
must negotiate TLS; use VERIFY_IDENTITY rather than REQUIRED, which encrypts without checking the server.
- TLS on every connection. TLS 1.2 is the minimum and TLS 1.3 is preferred; plaintext is never
offered. Verify the server, not just the encryption: use
ssl-mode=VERIFY_IDENTITY. See TLS and the CA bundle. - IP allow-list. The gateway checks the client address before authentication, on every plan.
Manage it under Network → Allow-list in the portal or with
PUT /v1/orgs/{org}/instances/{id}/network. - Credentials. Generated inside the cell, shown once, never stored by the control plane. Rotate them with an overlap window so nothing breaks.
- Secure hosting. Secure placement (HIPAA-ready) is a per-instance option once your organization has signed the BAA: dedicated nodes, customer-managed keys and immutable backups. See Secure hosting and the BAA.
- Staff access. Databasezy staff cannot read your data without a grant you issue. See data confidentiality.
Should I choose 8.4 or 8.0?
Section titled “Should I choose 8.4 or 8.0?”8.4 for anything new. 8.0 is past its upstream end of life and deprecated: new 8.0 instances are refused from 2027-01-05, with notices 90, 30 and 7 days ahead. Moving from 8.0 to 8.4 is not an in-place upgrade (8.4 rewrites the data dictionary, which 8.0 cannot read back): create an 8.4 instance and migrate into it, or restore an 8.0 backup into a new 8.4 instance, then switch your application over.
My client fails with a TLS error. What changed?
Section titled “My client fails with a TLS error. What changed?”Databasezy never accepts plaintext MySQL connections. Set --ssl-mode=VERIFY_IDENTITY (or your driver’s equivalent) and pass the CA bundle from the Connect tab if your driver does not use the system trust store. The mysql command-line client (8.x) also needs --tls-sni-servername=<host>: the gateway routes each connection by the TLS server name, which that client does not send by default (drivers and the mariadb client do).
Can I migrate from PlanetScale with zero downtime?
Section titled “Can I migrate from PlanetScale with zero downtime?”No. Vitess does not let an outside replica read the binlog, so a PlanetScale migration is copy only with a write freeze. See From PlanetScale.