In my recent work I had to dive into the Internals of the MySQL protocol.
Most of the protocol is described in Chapter 8. MySQL Client/Server Protocol. "Most" as it documents the state of MySQL 4.1.x before it was stable. It is interesting for all developers who want to extend the mysql-server or want to write a native connector to MySQL which isn't using libmysqlclient
.
Note: the protocol definition and the derived work at released under the terms of the GPL. See 8.1. Licensing Notice
Connection States
The MySQL protocol has two phases: auth
and query
.
The auth-phase is:
- client connects to the server
- servers sends handshake packet
- client sends a auth packet
- servers sends either OK or ERR
The afterwards the client and server enter the query-phase.
- Client sends a COM_* packet
- Server sends OK, ERR, EOF or a result-set
Packets
All the packets start with a 4-byte packet-header. It is made up of a 3-byte packet-length and a 1-byte sequence number. Whenever the client sends a new Query it resets the sequence-number to 0. Keep in mind that some statements might overflow the sequence-number (result-set with more than 255 fields)
If a packet is larger than 16MByte (3-byte length header) it is indicated by a packet length of 0x00ffffff
which indicates that the packet is finished by another packet until the packet-len is less than 16MByte. You will see this when you handle BLOBs
.
If you are sending 32MByte of data you will see 2 packets with 16MByte and a empty packet which is closing it:
\377\377\377\1
....
\377\377\377\2
....
\0\0\0\3
EOF packet
The EOF packet is pretty simple and usually used to close different sections in a result-set.
\5\0\0\5 [5 byte, packet 5]
\376\0\0\2\0 [EOF, flags: autocommit]
note: I splitted the stream into the logic pieces. It is using the octal notation used by strace.
The flags are taken from include/mysql_com.h
:
#define SERVER_STATUS_IN_TRANS 1 /* Transaction has started */
#define SERVER_STATUS_AUTOCOMMIT 2 /* Server in auto_commit mode */
#define SERVER_MORE_RESULTS_EXISTS 8 /* Multi query - next query exists */
#define SERVER_QUERY_NO_GOOD_INDEX_USED 16
#define SERVER_QUERY_NO_INDEX_USED 32
/*
The server was able to fulfill the clients request and opened a
read-only non-scrollable cursor for a query. This flag comes
in reply to COM_STMT_EXECUTE and COM_STMT_FETCH commands.
*/
#define SERVER_STATUS_CURSOR_EXISTS 64
/*
This flag is sent when a read-only cursor is exhausted, in reply to
COM_STMT_FETCH command.
*/
#define SERVER_STATUS_LAST_ROW_SENT 128
#define SERVER_STATUS_DB_DROPPED 256 /* A database was dropped */
#define SERVER_STATUS_NO_BACKSLASH_ESCAPES 512
SERVER_QUERY_NO_GOOD_INDEX_USED
and SERVER_QUERY_NO_INDEX_USED
are nice hints for the application.
Result Sets
For most other statements like INSERT
, UPDATE
, DELETE
you'll just get a OK
packet.
For SELECT
queries and FETCH
you can have a result-set. For the query like:
SELECT @@version_comment LIMIT 1;
... the result-set is splitted into 2 parts:
- the field description
- the rows
Both sections are finished with an EOF
packet. If the result-set is empty you might either have no result-set or just a field-description:
SELECT 1 FROM dual LIMIT 0;
will give you
\1\0\0\1 [1 byte, packet 1]
\1 [1 field]
\27\0\0\2 [23 bytes, packet 2]
\3def\0\0\0\0011\0\f?\0\1\0\0\0\10\201\0\0\0\0
\5\0\0\3 [5 bytes, packet 3]
\376\0\0\2\0 [EOF for fields]
\5\0\0\4 [5 bytes, packet 4]
\376\0\0\2\0 [EOF for rows]
For a real result-set you have:
\1\0\0\1
\1 [1 field]
\'\0\0\2
\3def - always \3def
\0
\0
\0
\21@@version_comment
\0 - org-name
\f - filler
\10\0 - charset
\34\0\0\0 - length (28 byte, max length of the field)
\375 - type (MYSQL_TYPE_VAR_STRING, see include/mysql_com.h)
\1\0 - flags (NOT_NULL_FLAG, see include/mysql_com.h)
\37 - decimals
\0\0 - filler
\5\0\0\3
\376\0\0\2\0 [EOF]
\35\0\0\4
\34MySQL Community Server (GPL)
\5\0\0\5
\376\0\0\2\0 [EOF]
Keep an eye on the EOF
packet as they might have flags which result in more packets to be fetched before the query is really finished.
SERVER_MORE_RESULTS_EXISTS
is used in multi-query result-sets to flag that there is another result-set coming up.SERVER_STATUS_CURSOR_EXISTS
is used to mark a short result-set for COM_STMT_EXECUTE and COM_STMT_FETCHSERVER_STATUS_LAST_ROW_SENT
is used to mark the end of the result-set on COM_STMT_FETCH
Some SELECT
statements get OK
instead:
SELECT 1 FROM dual INTO OUTFILE 'example.txt'
returns:
\7\0\0\1
\0\0\0\2\0\0\0
Replication Client
The documentation for the replication client protocol is a bit out-dated as it only covers MySQL 4.1 and older. To get a log-event-stream you have to send a [COM_BINLOG_DUMP]
command:
- COM_BINLOG_DUMP
- 4byte binlog-pos
- 2byte flags (BINLOG_DUMP_NON_BLOCK see sql/mysql_priv.h)
- 4byte slave-server-id
- nul-term binlog name
You can get the binlog-pos and the binlog-name with SHOW MASTER STATUS:
> SHOW MASTER STATUS;
+---------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+---------------------+----------+--------------+------------------+
| hostname-bin.000009 | 25075226 | | |
+---------------------+----------+--------------+------------------+
The slave-server-id
should be unique across the replication-tree. The flags
can only be BINLOG_DUMP_NON_BLOCK
for now which causes are EOF
packet to be sent if no new is available. Pretty much like a EAGAIN
in for a read()
syscall.
Log Events
Each log-event has the same header. After the error-code
field you might have a variable-size option field which is only used in 5.0 and later.
When you execute:
jan@localhost[world]> DROP TABLE t1;
... you get:
N\0\0\3
\0
g\255\7F - timestamp
\2 - QUERY_EVENT
\1\0\0\0 - server-id
M\0\0\0 - event-size
\263\0\0\0 - log-pos
\20\0 - flags
\2\0\0\0 - thread-id
\0\0\0\0 - query-time
\5 - str-len of default-db (world)
\0\0 - error-code on master-side
\32\0 - var-size-len (5.0 and later)
\0 - Q_FLAGS2_CODE
\0@\0\0 - flags (4byte)
\1 - Q_SQL_MODE_CODE
\0\0\0\0\0\0\0\0 (8byte)
\6 - Q_CATALOG_NZ_CODE
\3std (4byte)
\4 - Q_CHARSET_CODE
\10\0\10\0\10\0 (6byte)
world\0 -
drop table t1
The flags are defined in sql/log_event.h
.
Comments