Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
The data type you assign to a column affects storage size, index efficiency, and query behavior. Choosing a type that’s larger than necessary wastes space in every row and in every index that includes that column. Choosing a type that’s too small causes truncation errors or silent data loss.

Integer Types

MySQL has five integer types. Pick the smallest one that fits your expected range. INT UNSIGNED doubles the positive range to ~4.3B. Use UNSIGNED on primary keys and foreign keys that are always positive — it’s free extra range. BIGINT is twice the storage of INT. Don’t default to it for all IDs — INT handles over 2 billion rows, which is more than most tables ever reach.

String Types

VARCHAR vs CHAR

VARCHAR(n) stores only as many bytes as the string contains (plus 1-2 bytes for length). CHAR(n) always stores exactly n bytes, padding shorter strings with spaces. CHAR can be slightly faster for fixed-length values because no length prefix needs to be parsed. For anything variable, use VARCHAR.

VARCHAR vs TEXT

VARCHAR values up to the row size limit are stored inline in the clustered index page. TEXT (and BLOB) values are stored in overflow pages once they’re large enough. This means:
  • VARCHAR columns are included in indexes directly
  • TEXT columns can’t be fully indexed — you need a prefix length: INDEX (description(100))
Use VARCHAR for strings you know are under a few hundred characters. Use TEXT for long content (articles, comments, HTML) where inline storage doesn’t make sense.

Date and Time Types

The key distinction between DATETIME and TIMESTAMP:
  • TIMESTAMP stores values as UTC and converts to the session’s time_zone on retrieval. If your application operates in multiple timezones, TIMESTAMP handles the conversion automatically.
  • DATETIME stores the literal value with no timezone conversion — what you put in is what you get out.
  • TIMESTAMP has a 2038 limit (Unix timestamp overflow). For any date beyond 2038, use DATETIME.
For a full discussion of timezone handling, see Timestamps and Time Zones in MySQL.

Decimal and Floating Point

FLOAT and DOUBLE use binary floating-point, which can’t represent decimal fractions exactly. 0.1 + 0.2 in floating-point is 0.30000000000000004. For money, always use DECIMAL.

JSON

JSON columns store validated JSON and provide path-based query operators.
JSON columns can’t be indexed directly. To index a frequently-queried JSON field, use a generated column:

Frequently Asked Questions

Should I use INT or BIGINT for primary keys?

INT handles 2.1 billion rows as a signed integer, 4.3 billion unsigned. For most tables, INT UNSIGNED is enough. Use BIGINT if you genuinely expect to exceed that, or if you’re using UUID-style IDs that don’t fit in 4 bytes.

Can I use VARCHAR(255) everywhere to avoid thinking about it?

You can, but longer VARCHAR declarations produce wider index keys, which increases index memory usage and reduces how many index entries fit per page. Declare the realistic maximum — VARCHAR(100) for a name field, not VARCHAR(255). The actual storage cost matches the data length, but the index key length is fixed at the declared size.

What’s the practical difference between NULL and a default value?

NULL means “unknown” — it propagates through expressions and is excluded from aggregate functions. A default value like 0 or '' means “explicitly set to empty.” Pick NULL for genuinely absent data; pick a default for “not yet set” scenarios where you want the column to have a sensible value immediately. Mixing them creates inconsistency in queries.

Troubleshooting

See also