go-sql-driver/mysql · error
invalid DATETIME packet length
Error message
invalid DATETIME packet length %d
What it means
Raised by parseBinaryDateTime (utils.go:265) when decoding a DATETIME/TIMESTAMP/DATE value received via the MySQL BINARY protocol (prepared statements). The driver expects the value payload length (num) to be 0 (zero), 4 (DATE), 7 (DATETIME no fractional), or 11 (DATETIME with fractional); any other length is protocol corruption and cannot be decoded.
Solutions
- Verify the MySQL-compatible server and any proxy in the path speak the standard binary protocol; upgrade or patch the offending component.
- Avoid prepared statements for that query (use db.Exec/Query with interpolation-free text query path) to fall back to the text protocol and parseDateTime.
- Confirm the column is truly a temporal type and not an exotic type the server encodes differently.
- Capture the packet (MySQL general log / tcpdump) and file a bug against the proxy/server if the length is genuinely invalid.
Example fix
// before: prepared-statement binary path hits bad length
db.QueryRow(`SELECT created_at FROM t WHERE id=?`, id).Scan(&createdAt)
// after: bypass binary protocol by scanning raw, or use text query
var raw []byte
db.QueryRow(`SELECT created_at FROM t WHERE id=?`, id).Scan(&raw)
t, _ := time.Parse("2006-01-02 15:04:05", string(raw)) Defensive patterns
Strategy: try-catch
Validate before calling
// You cannot pre-validate server-sent binary payloads; instead detect a
// non-conforming server/proxy before relying on prepared-statement datetime.
func probeBinaryDateTime(db *sql.DB) error {
var raw []byte
return db.QueryRow(`SELECT CAST(NOW() AS DATETIME)`).Scan(&raw)
} Type guard
func isStandardBinaryDatetimeLen(n int) bool {
switch n {
case 0, 4, 7, 11:
return true
}
return false
} Try / catch
var t time.Time
if err := db.QueryRow(q, id).Scan(&t); err != nil {
if strings.Contains(err.Error(), "invalid DATETIME packet length") {
// fall back: read raw bytes and parse text form
var raw []byte
_ = db.QueryRow(`SELECT CAST(ts AS CHAR) FROM t WHERE id=?`, id).Scan(&raw)
t, _ = time.Parse("2006-01-02 15:04:05", string(raw))
}
} Prevention
- Pin known-good MySQL/Proxy versions; test datetime reads after server upgrades.
- Keep a text-protocol fallback query for temporal columns when targeting proxies.
- Report invalid binary lengths to the proxy/server vendor with a packet capture.
When it happens
Trigger: Using prepared statements (db.Query/QueryRow with args, or Prepare+Exec/Query) to SELECT a DATETIME/TIMESTAMP/DATE column with parseTime=true, where the server sends a binary value whose length is not 0/4/7/11. Reached via parseBinaryDateTime at packets.go:1416. Typical with non-conforming MySQL-compatible servers (some proxies, forks, or older protocol implementations) or when a packet desync occurred.
Common situations: Connecting through a misbehaving proxy/sharder (Vitess, old Aurora, DB2 compatibility layers), a server speaking an incompatible or corrupted binary protocol, memory/packet corruption, or a driver/server version mismatch after a server upgrade.
Related errors
- illegal length
- illegal packet length
- illegal TIME length
- invalid TIME packet length
- bad value for field: `%c`
AI-assisted analysis of go-sql-driver/mysql@03d76c7e07 (2026-08-07).
Data as JSON: /api/errors/1b8230956a1bdafa.
Report an issue: GitHub.
Appendix: source
Thrown at utils.go:265
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
0,
loc,
), nil
case 11:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
loc,
), nil
}
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
}
func appendDateTime(buf []byte, t time.Time, timeTruncate time.Duration) ([]byte, error) {
if timeTruncate > 0 {
t = t.Truncate(timeTruncate)
}
year, month, day := t.Date()
hour, min, sec := t.Clock()
nsec := t.Nanosecond()
if year < 1 || year > 9999 {
return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
}
year100 := year / 100
year1 := year % 100
var localBuf [len("2006-01-02T15:04:05.999999999")]byte // does not escapeView on GitHub (pinned to 03d76c7e07)