MySQL String Types: VARCHAR vs TEXT vs CHAR
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep as the tool, with the specific code_pattern being TEXT columns with UNIQUE or DEFAULT, or CHAR on variable-length fields. This is not caught by the compiler or default linters but requires a configured semgrep rule — placing it squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix is a column type swap (VARCHAR(255) for most, TEXT for large content, CHAR for fixed-length). This is a small refactor: an ALTER TABLE statement per affected column, potentially touching schema migration files, but not a cross-cutting codebase change. Slightly above a one-liner due to the utf8mb4 index limit gotcha that may require row format changes.
Closest to 'persistent productivity tax' (b5). The choice applies to all web and CLI contexts, and wrong column types affect every query, index, and migration touching those columns. Using TEXT for email/slug blocks default values and full-column indexes; using CHAR(255) wastes storage on every row. This imposes an ongoing tax on schema design and query performance but doesn't reshape the entire architecture.
Closest to 'serious trap' (t7). The misconception field states that TEXT and VARCHAR are considered interchangeable by many developers, yet TEXT cannot have a DEFAULT value, cannot be fully indexed, and stores data off the main row page — contradicting the intuitive assumption that they are equivalent string storage. The utf8mb4 + VARCHAR(255) index limit is an additional non-obvious gotcha. This contradicts expectations formed from other databases or general programming experience, warranting t7.
Also Known As
TL;DR
Explanation
CHAR(n) pads shorter values with spaces — slightly faster for fixed-length data. VARCHAR(n) stores actual length + 1-2 bytes overhead. TEXT (64KB), MEDIUMTEXT (16MB), LONGTEXT (4GB) store large content off the main row page — cannot be fully indexed without a prefix length and cannot have DEFAULT values. VARCHAR up to 767 bytes (InnoDB row format default) supports full-column indexing. For utf8mb4, the index limit is 191 characters on older InnoDB row formats.
Common Misconception
Why It Matters
Common Mistakes
- Using TEXT for email, name, or slug columns — no DEFAULT, no full-column index.
- Using CHAR(255) for variable-length data — pads every row regardless of actual content length.
- Forgetting that utf8mb4 VARCHAR(255) requires 255×4=1020 bytes — exceeds the 767-byte InnoDB index limit on older row formats.
Avoid When
- Avoid TEXT for columns that need DEFAULT values or full-column unique indexes.
- Avoid CHAR for variable-length content — every row is padded to the declared length.
When To Use
- Use VARCHAR(255) as the default for most string columns — fully indexable and flexible.
- Use TEXT/MEDIUMTEXT/LONGTEXT for article bodies, descriptions, or any content over 1KB.
- Use CHAR for truly fixed-length values: country codes (CHAR(2)), currency codes (CHAR(3)), UUIDs (CHAR(36)).
Code Examples
-- TEXT for short fields: no DEFAULT, no full index
email TEXT NOT NULL,
-- CHAR(100) wastes 100 bytes even for 'Bob'
name CHAR(100) NOT NULL
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
country_code CHAR(2) NOT NULL DEFAULT 'GB',
bio TEXT,
slug VARCHAR(100) NOT NULL UNIQUE
) ENGINE=InnoDB CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;