VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
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:
VARCHARcolumns are included in indexes directlyTEXTcolumns can’t be fully indexed — you need a prefix length:INDEX (description(100))
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:
TIMESTAMPstores values as UTC and converts to the session’stime_zoneon retrieval. If your application operates in multiple timezones,TIMESTAMPhandles the conversion automatically.DATETIMEstores the literal value with no timezone conversion — what you put in is what you get out.TIMESTAMPhas a 2038 limit (Unix timestamp overflow). For any date beyond 2038, useDATETIME.
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 longerVARCHAR 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
- Normalization in MySQL — schema design context for type decisions
- JSON in MySQL — the JSON column type as a flexible alternative to rigid schemas
- How MySQL Indexes Work — data type choice affects index size and efficiency
- Multi-dimensional range queries — the cube type for storing and querying n-dimensional geometric data

