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

  1. Verify the MySQL-compatible server and any proxy in the path speak the standard binary protocol; upgrade or patch the offending component.
  2. 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.
  3. Confirm the column is truly a temporal type and not an exotic type the server encodes differently.
  4. 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

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


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 escape

View on GitHub (pinned to 03d76c7e07)