MySQL.jl Documentation

Getting started

MySQL.jl is a client for MySQL and MariaDB servers. Since 2.0 it speaks the MySQL client/server wire protocol natively in Julia (on Reseau.jl TCP/TLS transports) — no C database connector is involved. If you are upgrading from 1.x, see Migrating from 1.x. Install it with:

] add MySQL

Connect through the DBInterface.jl API, which the rest of this page uses:

using MySQL, DBInterface, Dates   # MySQL re-exports Durations.Timestamp

conn = DBInterface.connect(MySQL.Connection, "localhost", "user", "password"; db="mydb")

Every host, "localhost" included, is dialed over TCP (port 3306 unless port is given). TLS is used whenever the server offers it (ssl_mode=:preferred); see Connection options for the full keyword list.

Queries and results

There are two ways to run SQL:

  • DBInterface.execute(conn, sql) runs a statement over the text protocol.
  • stmt = DBInterface.prepare(conn, sql); DBInterface.execute(stmt, params) prepares a statement once and executes it any number of times with parameters bound to its ? markers over the binary protocol. DBInterface.execute(conn, sql, params) is the one-shot form (prepare, execute, close).

Both return a MySQL.Cursor, a Tables.jl row source, so a result can be materialized as DataFrame(cursor), Tables.columntable(cursor), CSV.write("out.csv", cursor), or iterated row by row:

for row in DBInterface.execute(conn, "SELECT id, name FROM users")
    println(row.id, ": ", row.name)   # row.name, row[:name], row[2]
end

A row is valid only while it is the cursor's current row (the cursor is a forward-only iterator); collect the values you need before advancing. By default the whole result set is read at execute time (mysql_store_result=true); mysql_store_result=false streams rows as they are iterated, which keeps memory flat for large results but keeps the connection busy until the cursor is exhausted or closed. cursor.rows_affected and DBInterface.lastrowid(cursor) report a DML statement's outcome.

Parameters are bound by their Julia type: integers, floats, strings (String, DataString, any AbstractString), bytes (Vector{UInt8}, DataBytes), Date/Time/Timestamp/DateTime, MySQL.Bit, DataDecimals decimals, Bool, and missing/nothing for NULL.

Pass a tuple, named tuple, vector, or Tables.AbstractRow for multiple parameters. Values bind to ? markers in iteration order; names do not change that order. Named SQL markers such as :name are not supported. A bare scalar binds one parameter; use (bytes,) to bind a byte vector as one binary value. executemany takes one collection per parameter, as in the example below.

stmt = DBInterface.prepare(conn, "INSERT INTO users (name, joined) VALUES (?, ?)")
DBInterface.execute(stmt, ("alice", Date(2026, 1, 2)))
DBInterface.executemany(stmt, (name=["bob", "carol"], joined=[Date(2026, 1, 3), Date(2026, 1, 4)]))
DBInterface.close!(stmt)

DBInterface.transaction(conn) do ... end wraps the block in START TRANSACTION/COMMIT (ROLLBACK on an exception). DBInterface.executemultiple iterates the result sets of a CALL or of a multi-statement string (multi_statements=true) as distinct cursors. MySQL.load(table, conn, name) creates a table from a Tables.jl source's schema and inserts its rows in batches. Close the connection with DBInterface.close!(conn).

Connection options

DBInterface.connect(MySQL.Connection, host, user, password; kw...) accepts the keywords below (password=nothing can use an option-file password; "" overrides it with an empty password). Unknown keywords raise ArgumentError. Removed and unavailable keywords also raise ArgumentError unless their value is nothing or false.

Where and how to connect

keyworddefaultmeaning
db""default database (USE)
port3306TCP port; omitted/nothing uses file or opt-in environment defaults; 0 explicitly selects 3306
protocol:default:tcp, or the deferred :socket/:pipe (they raise a clear error)
bindnothinglocal interface/address to connect from
connect_timeoutnothingpositive integer seconds for the whole establishment (dial, TLS, authentication, charset bootstrap)
read_timeout, write_timeoutnothingpositive integer seconds per transport read/write; expiry closes the connection
reconnectfalseafter a command fails on a dead connection, the next command reconnects (never inside a transaction)
init_commandnothingSQL run after authentication and charset bootstrap; it must also succeed in expired-password sandbox mode
charset_name"utf8mb4"the only supported character set; the session is bootstrapped to utf8mb4
attrsclient name/version/OS/pidVector{Pair{String, String}} of connection attributes sent in the handshake
option_file, option_groupnothinga my.cnf/my.ini file (and group besides [client]) supplying host/user/password/port/database/TLS settings
read_default_file, read_default_groupfalsealso read the standard option-file locations
read_envfalselet MYSQL_TCP_PORT fill an omitted port (MYSQL_PWD is never read)
debugfalselog every packet at @debug level

Server-side behaviour flags

keyworddefaultmeaning
multi_statementsfalseallow "stmt1; stmt2" (consume the results with DBInterface.executemultiple)
found_rowsfalserows_affected counts matched rows instead of changed rows
no_schema, ignore_spacefalsethe corresponding server flags
local_filesfalseallow LOAD DATA LOCAL INFILE; requires local_infile_handler
local_infile_handlernothingfilename -> IO (or nothing to refuse) called when the server requests a local file
max_local_infile_bytes1 GiBcap on one upload
can_handle_expired_passwordsfalseconnect in sandbox mode with an expired password for ALTER USER USER() IDENTIFIED BY '…' / SET PASSWORD; normal queries fail with error 1820 until the password is reset

TLS

keyworddefaultmeaning
ssl_mode:preferred:disabled, :preferred (TLS when offered, no fallback after a failed handshake), :required, :verify_ca, :verify_identity; only the last two authenticate the server
ssl_ca / ssl_capathnothingCA bundle file / hashed CA directory (one of them); supplying one raises the default mode to :verify_ca
ssl_cert, ssl_keynothingclient certificate and key (mutual TLS)
ssl_server_namehostthe name used for SNI and certificate verification
tls_versionTLS 1.2 and 1.3e.g. "TLSv1.3"
ssl_enforce, ssl_verify_server_certnothingtrue requires TLS / identity verification; false does not lower the mode; contradictions with an explicit keyword ssl_mode are errors

Authentication (mysql_native_password, caching_sha2_password, sha256_password, and mysql_clear_password are supported)

keyworddefaultmeaning
default_authsupported server default, else caching_sha2_passwordplugin to answer the handshake with; an auth switch selects the account's plugin
get_server_public_keyfalsefetch the server's RSA key for caching_sha2/sha256 full authentication over plain TCP
server_public_keynothingpath of that key in PEM form
enable_cleartext_pluginfalseallow mysql_clear_password (PAM/LDAP accounts); default_auth="mysql_clear_password" also enables it
insecure_cleartext_authfalseallow cleartext authentication without :verify_identity

Result decoding

keyworddefaultmeaning
zero_dates:sentinel0000-00-00 values: :sentinel (Date(0) / Timestamp{P}(0, 1, 1)), :missing (dates become Union{Missing, T}), :error
time_typeDates.TimeTIME columns as Dates.Time (0 ≤ t < 24h) or Dates.Microsecond (signed, up to ±838 h)

Limits (received lengths are checked before growing their payload buffers; outgoing commands are checked after encoding and before sending)

keyworddefaultmeaning
max_allowed_packet16 MiBlargest packet sent or accepted
max_buffered_bytes256 MiBretained bytes of one buffered command (nothing = unlimited)
max_response_bytesnothingcap on a whole response, streamed rows included
max_columns, max_result_sets, max_metadata_bytes4096, 1024, 16 MiBcolumns per result or parameters per prepared statement; result sets and metadata bytes per command
max_preauth_packet, max_auth_rounds, max_auth_bytesmin(1 MiB, max_allowed_packet), 8, 64 KiBconnection-phase bounds
max_session_state_bytes1 MiBsession-state blocks in an OK packet, including command responses

Deprecated 1.x keywords (data_truncation, net_buffer_length, secure_auth, multi_results) are accepted with a warning and have no effect; see the migration guide for removed and not-yet-available ones.

Option-file rules

Option files are opt-in. option_file selects one file. read_default_file=true or read_default_group=true also reads the default locations. option_group without option_file selects those locations as well. On Unix these are /etc/my.cnf, /etc/mysql/my.cnf, and ~/.my.cnf, in that order. On Windows they are my.ini and my.cnf in %WINDIR%, then in C:\. The explicit file is read last. A missing explicit file is an error; missing default files are skipped. World-writable files on Unix and .mylogin.cnf login-path files are skipped with a warning.

The reader selects [client] and option_group, with case-insensitive group names. The last occurrence of an option in file order wins, including across selected groups. Quoted values, MySQL backslash escapes, # comments, and full-line ; comments are supported. !include, !includedir, and ?includedir raise an error. Unknown keys are ignored. This is a subset of the MySQL option-file format.

Supported file keys are host, user, password, port, database, connect-timeout, default-character-set, protocol, bind-address, socket, tls-version, ssl-mode, ssl-ca, ssl-capath, ssl-cert, and ssl-key. Underscores can replace hyphens. socket is accepted but unused. ssl-cipher, ssl-crl, and ssl-crlpath raise an error because these settings are unavailable. compress also raises an error unless its value is 0, OFF, or FALSE (case-insensitive).

Nonempty positional host/user values and non-nothing keywords override file values. An empty host/user allows file fallback. An empty password or database overrides the file with an empty value. MYSQL_TCP_PORT is used only with read_env=true and when neither a keyword nor a file supplies the port. MYSQL_PWD is never read.

For TLS, keyword ssl_mode wins over file ssl-mode. Without that keyword, ssl_verify_server_cert=true selects :verify_identity; ssl_enforce=true requires at least :required and preserves a stronger file mode. Otherwise the file mode wins. Without any mode selection, a CA selects :verify_ca; the remaining default is :preferred.

Types

Result columns decode to the Julia types below (Union{Missing, T} unless the column is NOT NULL; UNSIGNED integer columns map to the unsigned counterpart):

MySQLJulia
TINYINT, SMALLINT, MEDIUMINT/INT, BIGINTInt8, Int16, Int32, Int64
FLOAT, DOUBLEFloat32, Float64
DECIMAL/NUMERICMySQL.DecimalResult (exact, all 65 digits)
BIT(n)MySQL.Bit
DATE, TIMEDate, Time (or Microsecond with time_type)
DATETIME/TIMESTAMPTimestamp{Second}; with fsp 1–3 Timestamp{Millisecond}, 4–6 Timestamp{Microsecond}
YEARClong (unsigned)
CHAR/VARCHAR/TEXT, BINARY/VARBINARY, ENUM, SET, JSONDataString
BLOB, GEOMETRYDataBytes

DataString and DataBytes are DataStrings.jl's compact string and byte values (the Arrow Utf8View/BinaryView layout): a value of up to 12 bytes is stored inline, a longer one references the cursor's row buffer, so decoding a result copies no bytes and allocates nothing per value. DataString <: AbstractString behaves like String (equality, hashing, ordering, iteration, String(s) to copy out); DataBytes <: AbstractVector{UInt8} likewise (Vector{UInt8}(b) copies). A long value keeps the buffer it references alive — the whole result of a buffered cursor, or the arena a streaming cursor read its row into. Arenas have a 64 KiB target; a row can grow an arena past that size. String(s)/Vector{UInt8}(b) detaches a value, and DataStrings.materialize(column) detaches a whole column (as returned by Tables.columntable or a DataFrame), copying every value out to a String or Vector{UInt8} and keeping missings. Requesting String/Vector{UInt8} explicitly through the typed accessor (Tables.getcolumn(row, String, i, name)) still returns a copy. BINARY/VARBINARY keep the 1.x string mapping; their bytes need not be valid UTF-8.

Timestamp{P} is Durations.jl's Int64 count since the Unix epoch at resolution P (the type proposed for the Julia 1.14 Dates stdlib, which it becomes automatically there); MySQL re-exports it. It is an AbstractDateTime: Dates.year(ts), DateTime(ts), Date(ts), Time(ts), arithmetic with periods, and comparisons with DateTime/Date all work, and every MySQL value is represented exactly. Construct one with Timestamp{Microsecond}(2024, 2, 29, 13, 14, 15, 250, 500).

MySQL.juliatype computes the mapping for a wire type and its flags.

Errors

Server errors are thrown as MySQL.Error (or MySQL.StmtError from prepared-statement operations) with errno, msg, and sqlstate fields. A connection the server dropped reports 4031 when MySQL supplies an idle-disconnect ERR, or the classic client codes 2006 ("MySQL server has gone away") and 2013 ("Lost connection to MySQL server during query") when the transport closes. Protocol and server exceptions derive from MySQL.MySQLError: besides the server errors there are ProtocolError (the byte stream violated the protocol or a limit; the connection is closed), TimeoutError, AuthError, TLSNegotiationError, ConversionError (a value cannot be represented by the column's Julia type), and LocalInfileRefused. Invalid options and stale row access can raise ArgumentError; transport errors can also propagate.

API reference

Connections and results

MySQL.ConnectionType
MySQL.Connection

A MySQL connection. Obtain one with DBInterface.connect(MySQL.Connection, host, user, password; kw...); see MySQL.ConnectOptions for the accepted keywords (removed 1.x ones explain why they fail). Operations are serialized by the connection lock. The first task to consume a streaming cursor owns it; a transaction is owned by the task that starts it.

source
MySQL.StatementType
MySQL.Statement

A prepared statement on the native backend, from DBInterface.prepare(conn, sql). Execute it with DBInterface.execute(stmt, params); close it with DBInterface.close!(stmt) (the COMSTMTCLOSE is deferred to the next command, never sent from a finalizer).

source
MySQL.CursorType
MySQL.TextCursor{buffered}
MySQL.BinaryCursor{buffered}

The cursor returned by DBInterface.execute: TextCursor for execute(conn, sql) (text protocol), BinaryCursor for execute(stmt, params) (binary protocol). It iterates rows and satisfies the Tables.jl row interface. buffered=true (mysql_store_result=true, the default) reads the whole result set at execute time under max_buffered_bytes; buffered=false streams rows on each iterate and ties up the connection until exhausted. A row is valid only while it is the cursor's current row; the values taken from it (including DataString/DataBytes views) stay valid.

source
MySQL.ConnectOptionsType
ConnectOptions

Validated, fully resolved connection parameters (see ConnectOptions(host, user, password; kw...)).

source
DBInterface.connectFunction
DBInterface.connect(MySQL.Connection, host, user, passwd=nothing; db=nothing, port=nothing, kw...)

Connects to a MySQL server. Keywords are the 1.x connection options plus ssl_mode=:preferred, get_server_public_key, tls_version, zero_dates, time_type, local_infile_handler, max_buffered_bytes, …; see MySQL.ConnectOptions. An omitted db/port falls back to the option files' database/port (when option files are read), like host/user/password.

source
DBInterface.close!Function
DBInterface.close!(conn)

Sends COM_QUIT (best effort) and closes the transport. Idempotent; unfinished streaming cursors become invalid. Buffered cursors retain their own bytes and remain readable.

source
DBInterface.close!(c::MySQL.Cursor)

Closes c and invalidates its current row. Further iteration yields no rows, for both buffered and streaming cursors. Repeated calls have no effect.

If c still owns a pending response, discards the remaining rows and result sets for its command.

source
DBInterface.close!(stmt::MySQL.Statement)

Closes the prepared statement. The COMSTMTCLOSE is parked and sent before the next command (never from a finalizer). Idempotent.

source
DBInterface.executeFunction
DBInterface.execute(conn::MySQL.Connection, sql; mysql_store_result=true) -> TextCursor

Runs sql with the text protocol and returns a cursor over the first result. With mysql_store_result=false rows are streamed (the connection is busy until the cursor is exhausted or closed). Further results of a multi-statement or CALL response are discarded by the next operation; use DBInterface.executemultiple to consume them. Passing params prepares, executes and returns a binary-protocol cursor bound to a one-shot statement.

source
DBInterface.execute(stmt::MySQL.Statement, params=(); mysql_store_result=true) -> BinaryCursor

Executes the prepared statement with params bound as the ? markers and returns a binary-protocol cursor. A tuple, named tuple, vector, or Tables.AbstractRow supplies values in iteration order; names do not select SQL parameters. A bare scalar binds one parameter. Wrap a binary byte vector as (bytes,) to bind it as one value. Named SQL markers such as :name are not supported. mysql_store_result=false streams rows (the connection is busy until the cursor is exhausted or closed).

source
DBInterface.executemultipleFunction
DBInterface.executemultiple(stmt::MySQL.Statement, params=(); kw...) -> Cursors

Iterates every result set of a prepared CALL (or multi-result statement) as a distinct binary cursor, like the connection-level executemultiple.

source
DBInterface.prepareFunction
DBInterface.prepare(conn::MySQL.Connection, sql) -> Statement

Prepares sql on the server and returns a Statement.

source
DBInterface.transactionFunction
DBInterface.transaction(f, conn)

Runs f() inside START TRANSACTION / COMMIT (or ROLLBACK when f throws) and returns f()'s value. The connection lock is held for the whole callback: other tasks block until the transaction ends, so f must not wait on tasks that need this connection.

source
DBInterface.lastrowidFunction
DBInterface.lastrowid(c::MySQL.Cursor)

The last_insert_id the server reported in this cursor's own OK packet (the DML result, or the result-set terminator), not the connection's current state.

source

Driver helpers

MySQL.pingFunction
MySQL.ping(conn) -> Bool

COM_PING round trip; throws when the connection is unusable.

source
MySQL.connection_idFunction
MySQL.connection_id(conn) -> Int

The server-side id of this connection (what CONNECTION_ID() returns), e.g. to cancel a long-running statement with KILL QUERY <id> from another connection.

source
MySQL.server_versionFunction
MySQL.server_version(conn) -> VersionNumber

The server version announced in the greeting (a MariaDB 5.5.5- prefix is stripped), for gating on server features; MySQL.server_kind(conn) tells :mysql from :mariadb.

source
MySQL.server_kindFunction
MySQL.server_kind(conn) -> Symbol

:mysql, :mariadb, :tidb, or :vitess, detected from the greeting.

source
MySQL.escapeFunction
MySQL.escape(conn, str) -> String

Escapes str for use inside a single-quoted SQL literal on this connection's character set (utf8mb4): \, ', ", NUL, newline, carriage return and Control-Z are backslash-escaped; under the session's NO_BACKSLASH_ESCAPES mode only ' is doubled.

source
MySQL.send_long_data!Function
MySQL.send_long_data!(stmt, parameter_number, data)

Sends one copied string or byte chunk for the zero-based prepared-statement parameter number. Repeated calls append chunks. The next execute omits that parameter's inline value and retains the copied chunks until its first response, so a 1615 or reconnect re-prepare can replay them.

source
MySQL.reset_statement!Function
MySQL.reset_statement!(stmt)

Resets a prepared statement's accumulated long data. The statement id and cached parameter signature remain valid when the session generation did not change.

source
MySQL.loadFunction
MySQL.load(table, conn, name; append=true, quoteidentifiers=true, limit=typemax(Int64), batchsize=1000, createtableclause=nothing, coltypes=Dict(), columnsuffix=Dict(), auto_increment_primary_key_name=nothing, debug=false)
table |> MySQL.load(conn, name; kw...)

Loads a Tables.jl source table into the table name of the database conn is connected to, and returns the (quoted) table name.

It first detects the Tables.Schema of the table source and generates a CREATE TABLE statement with the appropriate column names and types. If no table name is provided, one will be autogenerated, like mysql_xxxxx. The CREATE TABLE clause can be provided manually by passing the createtableclause keyword argument (default "CREATE TABLE IF NOT EXISTS" with append=true, else "CREATE TABLE"), which would allow specifying a temporary table. With append=false the existing rows are deleted before the new ones are inserted. Column types can be overridden by providing the coltypes keyword argument as a Dict of column name (given as a Symbol) to a string of the SQL type. This allows, for example, using a LONGBLOB instead of BLOB for large binary data by doing coltypes=Dict(:Photo => "LONGBLOB"). Column definitions can also be enhanced by providing arguments to columnsuffix as a Dict of column name (given as a Symbol) to a string of the enhancement that will come after name and type like [column name] [column type] enhancements. This allows, for example, specifying the charset of a string column by doing something like columnsuffix=Dict(:Name => "CHARACTER SET utf8mb4"). auto_increment_primary_key_name adds an INT AUTO_INCREMENT PRIMARY KEY column of that name in front of the source columns.

Rows are inserted inside one transaction with prepared multi-row INSERT statements of up to batchsize rows each (fewer when a batch would approach the packet limit, and at most 65535 bound values per statement, further bounded by max_columns); limit stops after that many rows. The packet budget uses the client's max_allowed_packet option; set it no higher than the server's value. Byte vectors are copied before advancing the source.

debug=true logs the generated statements without row values; debug=:values also logs each inserted row's values.

Do note that databases vary wildly in requirements for CREATE TABLE and column definitions so it can be extremely difficult to load data generically. You may just need to tweak some of the provided keyword arguments, but you may also need to execute the CREATE TABLE and INSERT statements yourself. If you run into issues, you can open an issue and we can see if there's something we can do to make it easier to use this function.

source
MySQL.juliatypeFunction
MySQL.juliatype(field_type, notnullable, isunsigned, isbinary, decimals=6) -> Type

The Julia type a result column decodes to, given its wire type, flags, and fractional precision: unsigned integer widening, binary BLOB (DataBytes) vs text (DataString), DATETIME/TIMESTAMP as Timestamp{P} per decimals (see MySQL.timestamp_type), exact DataDecimals values for DECIMAL, and Union{Missing, T} for nullable columns.

source
juliatype(def::Protocol.ColumnDef, opts::ResultOptions) -> Type

The column's Julia type: MySQL.juliatype applied to the wire type, flags, and fractional precision (decimals), then the decoding policies: time_type, and zero_dates=:missing widening every date column to Union{Missing, T} regardless of NOT NULL.

source

Value types

MySQL.BitType
MySQL.Bit

The value of a BIT(n) column (n ≤ 64): the big-endian value of all bytes the server sent, stored in bits::UInt64. (MySQL.API.Bit before 2.0.)

source
MySQL.DecimalResultType
MySQL.DecimalResult

The type DECIMAL/NUMERIC columns decode to: DataDecimals.DecimalValue{DataDecimals.Int256}, which holds all 65 digits and the column's scale exactly.

source
MySQL.timestamp_typeFunction
MySQL.timestamp_type(decimals) -> Type{<:Timestamp}

The Timestamp{P} a DATETIME/TIMESTAMP column with decimals fractional-second digits (its fsp, 0–6) decodes to: Timestamp{Second} for 0, Timestamp{Millisecond} for 1–3, Timestamp{Microsecond} for 4–6. Every MySQL value is represented exactly.

source

Errors

MySQL.MySQLErrorType
MySQLError

Root of the native backend's exception hierarchy.

  • ServerError (Error, StmtError): a server ERR or a client connection-loss error
  • ProtocolError: invalid protocol state, bytes, or a resource limit; wire faults close the connection
  • AuthError / UnsupportedAuthError: authentication policy or plugin problems
  • TimeoutError: a deadline expired
  • ConversionError: a wire value cannot be represented by the requested Julia type
  • TLSNegotiationError: the TLS handshake failed while establishing the connection
  • LocalInfileRefused: the LOCAL INFILE handler declined a server request
source
MySQL.ErrorType
Error(errno, msg, sqlstate="")

Server ERR or client connection-loss error. errno::Cuint and msg keep the field names and types of the Connector/C-backed MySQL.API.Error; sqlstate is new.

source
MySQL.StmtErrorType
StmtError(errno, msg, sqlstate="")

Server ERR packet raised by prepared-statement operations (distinct type on purpose so 1.x-style @test_throws MySQL.StmtError dispatch keeps working; the 1.x name was MySQL.API.StmtError).

source

Internal implementation

MySQL.Protocol is documented for maintainers. It is not part of the stable user API.

MySQL.ProtocolModule
MySQL.Protocol

Native implementation of the MySQL client/server wire protocol on top of Reseau transports: constants generated from the server headers, bounded codecs, packet framing with reassembly, the phase machine, handshake and capability negotiation, authentication plugins (mysql_native_password, caching_sha2_password, sha256_password, mysql_clear_password) with OpenSSL-backed RSA-OAEP, STARTTLS, generic responses, column definitions, text and binary row scanning, and the command/response framing of COMQUERY, the COMSTMT_* family, LOCAL INFILE and the simple commands. It has no DBInterface/Tables dependency; the MySQL driver layer (value decoding, connections, cursors, statements) builds on it. See docs/protocol-notes.md.

source