Key Takeaways

  • MySQL's 30+ data types span 4 families: numeric, string, date/time, and JSON. Picking the wrong one wastes storage and introduces data errors that compound over time.
  • Never use FLOAT or DOUBLE for money. Both use IEEE 754 binary floating-point, which can't represent most decimal fractions exactly. Use DECIMAL(10,2) instead.
  • TIMESTAMP auto-converts to UTC and has a hard 2038-01-19 ceiling. DATETIME stores wall-clock time with no conversion and supports dates through year 9999.
  • INT UNSIGNED covers ~4.3 billion rows. Switch to BIGINT UNSIGNED for event logs or high-volume tables before you hit that limit.
SQL code on a monitor screen representing MySQL database schema design and data type selection
Choosing the right column type at schema design time is far cheaper than migrating a live table later.

Numeric Types

MySQL's numeric types split into two groups: exact types (INT, BIGINT, DECIMAL) and approximate types (FLOAT, DOUBLE). The distinction matters most for financial data. The MySQL 8.0 Reference Manual documents that FLOAT and DOUBLE use IEEE 754 binary representation, which can't precisely store most decimal fractions — making them unsafe for any monetary column.

Type Storage Range (signed) Use for
TINYINT 1 byte -128 to 127 Booleans, small status flags
SMALLINT 2 bytes -32,768 to 32,767 Small counters, age, year
INT 4 bytes -2.1B to 2.1B General-purpose IDs and counts
BIGINT 8 bytes ±9.2 quintillion High-volume tables, snowflake IDs
DECIMAL(p,s) Variable Exact Money, measurements requiring precision
FLOAT / DOUBLE 4 / 8 bytes Approximate Scientific values where small errors are acceptable
MySQL Numeric Type Storage Sizes Numeric Type — Storage Size (bytes) TINYINT 1 byte SMALLINT 2 bytes INT 4 bytes FLOAT 4 bytes (approx.) DECIMAL(10,2) ~6 bytes (exact) BIGINT 8 bytes DOUBLE 8 bytes (approx.) Integer (exact) Float (approximate) Fixed-point (exact) Source: MySQL 8.0 Reference Manual — dev.mysql.com/doc/refman/8.0/en/storage-requirements.html
Storage bytes per MySQL numeric type. Green = exact integers, yellow = approximate floating-point, blue = fixed-point DECIMAL.

Key rules:

  • Use TINYINT(1) for booleans — MySQL's conventional boolean representation.
  • Use INT UNSIGNED for auto-increment primary keys (doubles the positive range to ~4.3 billion).
  • Use BIGINT UNSIGNED for tables that may grow very large.
  • Never use FLOAT or DOUBLE for monetary values. Floating-point imprecision causes rounding errors in financial calculations. Use DECIMAL(10, 2) instead.

MySQL stores FLOAT and DOUBLE using the IEEE 754 binary floating-point standard, which cannot precisely represent most decimal fractions (MySQL 8.0 Reference Manual). For monetary calculations, DECIMAL(10,2) stores exact values and prevents the rounding errors that floating-point types introduce in financial totals.

String Types

VARCHAR covers most string storage needs. It's variable-width: MySQL allocates only what the content requires, plus 1-2 bytes of length overhead. CHAR, by contrast, always occupies exactly n bytes — a 10-character value in a CHAR(50) column still consumes all 50 bytes. That fixed width makes CHAR marginally faster to index, which is why the MySQL 8.0 Reference Manual recommends it for fixed-length data like ISO country codes (CHAR(2)) or MD5 hashes (CHAR(32)).

Type Max size Use for
CHAR(n) 255 chars Fixed-length strings: country codes, hashes, status enums
VARCHAR(n) 65,535 bytes Variable-length strings: names, emails, URLs, titles
TEXT 65,535 bytes Long content: descriptions, comments, HTML
MEDIUMTEXT 16 MB Large documents, article bodies
LONGTEXT 4 GB Very large content (logs, serialised data)

Key rules:

  • Use VARCHAR(n) for most string columns. Set n to a realistic maximum: VARCHAR(255) for names and emails, VARCHAR(2048) for URLs.
  • Use CHAR(n) only when the value is always the same length. It's marginally faster to index than VARCHAR.
  • Don't put TEXT columns in WHERE clauses without a full-text index. It forces a full table scan regardless of how specific your filter is.
  • Avoid ENUM. It's inflexible to alter and opaque to external tools. Use a VARCHAR with a CHECK constraint, or a separate lookup table instead.

VARCHAR and CHAR differ primarily in storage behavior: CHAR(n) always occupies exactly n bytes, padded with spaces, while VARCHAR(n) uses 1 byte of overhead for values up to 255 characters plus actual content length (MySQL 8.0 Reference Manual). For columns where every value is exactly the same length, CHAR is marginally more efficient to index than VARCHAR.

Date and Time Types

The TIMESTAMP versus DATETIME decision has real production consequences. TIMESTAMP stores every value in UTC and reconverts to the session timezone on retrieval, making it automatic for audit columns like created_at. Its hard upper limit is 2038-01-19 — the point where a 32-bit Unix timestamp overflows. Any application storing future dates past that boundary must use DATETIME, which supports dates through year 9999 (MySQL 8.0 Reference Manual).

Open desk calendar illustrating MySQL date and time data type selection for created_at and scheduled event columns
Use TIMESTAMP for audit columns; use DATETIME when you need timezone-independent storage or dates beyond 2038.
Type Range Use for
DATE 1000-01-01 to 9999-12-31 Dates without time: birthdays, deadlines
DATETIME 1000-01-01 to 9999-12-31 Timestamps stored in application timezone
TIMESTAMP 1970-01-01 to 2038-01-19 Audit timestamps stored in UTC, auto-converted on retrieval
TIME -838:59:59 to 838:59:59 Durations, time-of-day without date
YEAR 1901 to 2155 Year-only values

Key rules:

  • Use TIMESTAMP for created_at and updated_at audit columns. It auto-converts to UTC on storage and back to the session timezone on retrieval.
  • Use DATETIME when you need to store a specific wall-clock time without timezone conversion, e.g., a scheduled event that should fire at 9am regardless of timezone.
  • TIMESTAMP has a 2038 ceiling. For future dates beyond that, use DATETIME.

MySQL's TIMESTAMP type stores values internally as UTC and converts to the current session timezone on retrieval (MySQL 8.0 Reference Manual). Its upper limit is 2038-01-19 03:14:07 UTC, the 32-bit signed integer overflow of Unix time. Applications storing reservation or event dates beyond 2038 must use DATETIME, which has no such ceiling.

JSON Type

MySQL 5.7 introduced native JSON storage; MySQL 8.0 expanded the operator set considerably. A JSON column validates syntax on insert and rejects malformed data outright, preventing the silent corruption that storing JSON in a TEXT column allows. Path expressions use dot notation: data->'$.key' is shorthand for JSON_EXTRACT(data, '$.key'), and the ->> operator returns an unquoted scalar value directly.

-- Read a single JSON path value
SELECT config->'$.theme' FROM user_settings WHERE user_id = 1;

-- Unquoted scalar (MySQL 5.7.13+)
SELECT config->>'$.theme' FROM user_settings WHERE user_id = 1;

-- Filter rows by JSON value
SELECT * FROM users WHERE preferences->>'$.notifications' = 'true';

Use JSON for truly variable or schema-less data: feature flags, user preferences, integration payloads. Don't use it to avoid modelling your data properly. If the same key appears in every row, it should be a regular column. JSON columns can't be indexed directly — only generated columns derived from JSON paths can be indexed — so querying inside JSON at scale requires careful planning. See our database schema examples for how to mix JSON columns with conventional structure.

MySQL's native JSON type validates document syntax at insert time and enables path-based queries using the -> and ->> operators, introduced in MySQL 5.7 and expanded in 8.0 (MySQL 8.0 Reference Manual). Unlike TEXT storage, a JSON column rejects malformed input and permits selective path extraction without parsing the full document in application code.

What Are the Most Common MySQL Data Type Mistakes?

Three type selection errors show up repeatedly in production schemas. They're easy to avoid at design time and expensive to fix once a table has millions of rows.

1. FLOAT or DOUBLE for monetary values. This is the most damaging one. A column defined as FLOAT can't store 0.1 exactly — it stores the nearest IEEE 754 approximation. Sum enough rows and the total drifts from the correct value. Use DECIMAL(10, 2) for any price, fee, or financial amount. No exceptions. The database normalization guide covers how this fits into a well-structured financial schema.

2. TEXT columns in WHERE clauses without a FULLTEXT index. A query like WHERE body LIKE '%keyword%' against a TEXT column does a full table scan every time. On a table with 50,000 rows that's slow; on a table with 5 million rows it's a timeout. Add a FULLTEXT index, store a searchable excerpt in a VARCHAR column, or move search to a dedicated tool.

3. ENUM instead of a lookup table. ENUM enforces allowed values at the database level, which sounds useful. The problem is ALTER TABLE. Adding a new ENUM value in MySQL 5.x rewrites the entire table — a blocking operation on a live database. In MySQL 8.0 this is instant for appended values, but the opaque storage still confuses ORMs and external tooling. A VARCHAR(50) with a CHECK constraint, or a foreign key to a status_types lookup table, is far more maintainable. Your future self will thank you when the business adds a new status at 11pm on a Friday.

Quick Reference: Common Column Patterns

  • Primary key: INT UNSIGNED NOT NULL AUTO_INCREMENT
  • Large-table primary key: BIGINT UNSIGNED NOT NULL AUTO_INCREMENT
  • UUID / GUID: CHAR(36) NOT NULL (or BINARY(16) with UUID_TO_BIN() in MySQL 8.0+ for storage efficiency)
  • Email address: VARCHAR(255) NOT NULL UNIQUE
  • Password hash: VARCHAR(255) NOT NULL
  • URL slug: VARCHAR(255) NOT NULL
  • Price / monetary value: DECIMAL(10, 2) NOT NULL
  • Boolean flag: TINYINT(1) NOT NULL DEFAULT 0
  • Created/updated timestamps: TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
  • Long-form text: TEXT
  • Foreign key: Same type as the referenced primary key, e.g. INT UNSIGNED NOT NULL

Video Resource

Frequently Asked Questions

What MySQL data type should I use for storing money?

Use DECIMAL(p, s) — for example DECIMAL(10, 2) for two decimal places. Never use FLOAT or DOUBLE for money. Both use IEEE 754 binary floating-point, which cannot represent most decimal fractions exactly, causing rounding errors that accumulate in financial totals.

What is the difference between DATETIME and TIMESTAMP in MySQL?

TIMESTAMP stores values in UTC and automatically converts to the session timezone on retrieval, making it ideal for created_at and updated_at audit columns. DATETIME stores the literal wall-clock time without timezone conversion and has a wider date range (up to year 9999 vs TIMESTAMP's 2038-01-19 limit).

What MySQL type should I use for boolean columns?

MySQL has no native boolean type. The convention is TINYINT(1), which stores 0 (false) or 1 (true). ORMs like Laravel and Rails treat TINYINT(1) as a boolean automatically. MySQL 8.0+ also accepts BOOLEAN as a synonym for TINYINT(1).

When should I use VARCHAR vs TEXT in MySQL?

Use VARCHAR(n) for short strings where you know the maximum length: names, emails, URLs, slugs. Use TEXT for long-form content such as article bodies, descriptions, or HTML where the length is unpredictable. Avoid using TEXT columns in WHERE clauses without a full-text index, as it forces a full table scan.

What MySQL data type should I use for a primary key?

INT UNSIGNED NOT NULL AUTO_INCREMENT is the standard choice for most tables, supporting up to approximately 4.3 billion rows. Use BIGINT UNSIGNED NOT NULL AUTO_INCREMENT for tables expected to grow very large, such as event logs or high-volume transactional tables.

Why should I avoid ENUM in MySQL?

ENUM stores allowed values as a one- or two-byte integer mapped to a list of strings. Adding a new value requires an ALTER TABLE that rewrites the entire table in older MySQL versions, causing downtime on large tables. ENUM values are also opaque to ORMs and external tools. A VARCHAR column with a CHECK constraint, or a separate lookup table, is more maintainable and easier to extend.

What MySQL data type should I use for a UUID?

CHAR(36) stores the standard hyphenated UUID string. For storage efficiency, MySQL 8.0+ provides UUID_TO_BIN() and BIN_TO_UUID() to convert a UUID into BINARY(16), halving the storage compared to CHAR(36) and improving index performance. Use CHAR(36) for readability; use BINARY(16) for high-volume tables where index size matters.