Through TenantsDB, each tenant gets their own MySQL database with the same schema, deployed from a blueprint. The proxy handles routing, TLS, query logging, and settings enforcement. Your app connects with any standard MySQL driver. No SDK needed.
The proxy supports the MySQL wire protocol including prepared statements (binary protocol) and text protocol queries. Control workspaces give your application a managed backend database with full DDL access. Tenant workspaces track schema changes as blueprints for deployment.
Unlike PostgreSQL where TLS is configured via URL parameter, MySQL TLS is configured differently per driver. Each ORM example below includes the correct TLS setup for that language.
mysql -h mysql.tenantsdb.com -P 3306 \ -u tdb_2abf90d3 -ptdb_d2bf66ed7898c448 \ controlplane_workspace --ssl-mode=REQUIRED
Control mode workspaces accept all DDL immediately. No blueprint versioning, no deployment step. Schema changes take effect as soon as you run them. Use this for your application's own tables that are not per-tenant.
mysql -h mysql.tenantsdb.com -P 3306 \ -u tdb_2abf90d3 -ptdb_d2bf66ed7898c448 \ myapp_workspace --ssl-mode=REQUIRED
Every CREATE TABLE, ALTER TABLE, or other DDL statement you run here is captured as a blueprint version. Deploy it to all tenants with tdb deployments create --blueprint myapp --all.
mysql -h mysql.tenantsdb.com -P 3306 \ -u tdb_2abf90d3 -ptdb_d2bf66ed7898c448 \ myapp__acme --ssl-mode=REQUIRED
mysql -h mysql.tenantsdb.com -P 3306 \ -u tdb_2abf90d3 -ptdb_d2bf66ed7898c448 \ myapp__globex --ssl-mode=REQUIRED
DDL not allowed on tenant databases - use workspace mode.CREATE TABLE accounts ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, balance DECIMAL(15,2) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO accounts (name, email, balance) VALUES ('Alice', '[email protected]', 1000), ('Bob', '[email protected]', 2000);
CREATE TABLE, ALTER TABLE, etc.) are tracked as blueprint changes. DML statements (INSERT, UPDATE, DELETE) run in the workspace only and are not deployed to tenants.
You can also import an existing schema from another database or use a template. See tdb workspaces schema --help for all options.
const { Sequelize, DataTypes } = require('sequelize'); const sequelize = new Sequelize('myapp__acme', 'tdb_2abf90d3', 'tdb_d2bf66ed7898c448', { host: 'mysql.tenantsdb.com', port: 3306, dialect: 'mysql', dialectOptions: { ssl: { rejectUnauthorized: false } } }); // Define models const Account = sequelize.define('Account', { name: { type: DataTypes.STRING(255), allowNull: false }, email: { type: DataTypes.STRING(255), allowNull: false, unique: true }, balance: { type: DataTypes.DECIMAL(15, 2), defaultValue: 0 }, }, { tableName: 'accounts', timestamps: true }); // Query const accounts = await Account.findAll(); await Account.create({ name: 'Alice', email: '[email protected]', balance: 1000 });
npm install sequelize mysql2
mysql2 internally. TLS is configured via dialectOptions.ssl.import ssl from sqlalchemy import create_engine, Column, Integer, String, Numeric, DateTime from sqlalchemy.orm import declarative_base, Session from datetime import datetime Base = declarative_base() class Account(Base): __tablename__ = 'accounts' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(255), nullable=False) email = Column(String(255), unique=True, nullable=False) balance = Column(Numeric(15, 2), default=0) created_at = Column(DateTime, default=datetime.utcnow) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE engine = create_engine( "mysql+pymysql://tdb_2abf90d3:[email protected]:3306/myapp__acme", connect_args={"ssl": ctx} ) # Query with Session(engine) as s: accounts = s.query(Account).all() s.add(Account(name='Alice', email='[email protected]', balance=1000)) s.commit()
pip install sqlalchemy pymysql
connect_args. The URL does not include a TLS parameter.package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "tdb_2abf90d3:tdb_d2bf66ed7898c448@tcp(mysql.tenantsdb.com:3306)/myapp__acme?tls=true", ) if err != nil { panic(err) } defer db.Close() // Query rows, _ := db.Query("SELECT id, name, balance FROM accounts") for rows.Next() { var id int var name string var balance float64 rows.Scan(&id, &name, &balance) fmt.Printf("%d: %s ($%.2f)\n", id, name, balance) } }
go get github.com/go-sql-driver/mysql
go-sql-driver is the only MySQL driver where ?tls=true works in the connection string. ORMs like GORM use this driver under the hood.| Driver / ORM | TLS Configuration |
|---|---|
| mysql CLI | --ssl-mode=REQUIRED |
| Sequelize (mysql2) | dialectOptions: { ssl: { rejectUnauthorized: false } } |
| SQLAlchemy (pymysql) | connect_args={"ssl": ctx} with ssl.create_default_context() |
| Go (go-sql-driver) | ?tls=true in connection string |
mysql.tenantsdb.com require TLS. Connections without TLS are rejected with a clear error message.The proxy forwards the MySQL wire protocol and supports both text protocol (COM_QUERY) and binary protocol (COM_STMT_EXECUTE) for prepared statements. All standard MySQL clients and ORMs work through the proxy.
The proxy enforces max_rows_per_query, query_timeout_ms, and max_connections at the proxy level. These are configured per workspace and apply to all tenants using that blueprint.
For bulk imports, use tdb workspaces import-full which connects directly to the source database, splits data by routing field, and creates tenants automatically. Multi-row INSERT statements work through the proxy for ongoing batch operations.
LOAD DATA LOCAL is not supported through the proxy. Use the import endpoint or INSERT statements instead.Statements are limited to 16MB per operation through the proxy due to MySQL's single-packet wire protocol boundary. Operations exceeding this return a clear error message. For large binary data, store files in object storage and keep references in the database.