Skip to content

MySQL Tutorial MySQL Data Types

Choosing the Right Data Type

Lesson 65 of 308 4 min read By Deepak Paswan Updated Oct 5, 2026

You have seen the types one by one. Here is the decision guide you can keep next to your editor.

The questions to ask

  1. What kind of thing is it? A number, text, date, true/false, structured data, raw bytes?
  2. Do I do arithmetic on it? If not, it may not be a number. A phone number or postal code is text, even though it looks like a number. It can start with 0, contain + or spaces, and you never add two phone numbers.
  3. How large can it get? Think about the biggest realistic value, then add room for growth.
  4. Can it be unknown? Decide NULL or NOT NULL.
  5. Will I search, sort or join on it? Then keep it small. It will be in an index.

Cheat sheet

DataRecommended typeWhy
Primary keyINT UNSIGNED AUTO_INCREMENT or BIGINT UNSIGNEDCompact, fast, sequential
Public IDBINARY(16) UUID, next to the integer keyNot guessable
NameVARCHAR(100)Enough for real names
EmailVARCHAR(255)Standard maximum is 254
Password hashVARCHAR(255)Hash formats grow over time
Phone numberVARCHAR(20)Text, with country code
Postal codeVARCHAR(10)Text, because of leading zeros and letters
Country / currency codeCHAR(2) / CHAR(3)Fixed length
Slug, URL pathVARCHAR(100) to VARCHAR(255)
Long URLVARCHAR(2048) or TEXT
Article bodyMEDIUMTEXTUp to 16 MB
MoneyDECIMAL(10,2) or an integer in the smallest unitExact
Quantity, stockINT UNSIGNED (or SMALLINT if small)
Rating 1 to 5TINYINT UNSIGNEDOne byte
PercentageDECIMAL(5,2)
Latitude / longitudeDECIMAL(9,6)
Yes / noBOOLEAN (TINYINT(1)) with NOT NULL
Small stable listENUM, or VARCHAR plus CHECK
Growing listLookup table and foreign key
Date without timeDATE
Moment in timeDATETIME (UTC) or TIMESTAMP
IP addressVARBINARY(16)INET6_ATON
HashBINARY(32) for SHA-256Or CHAR(64)
Flexible attributesJSONNot for data you filter on
FilesPath in VARCHAR; file elsewhere

Mistakes seen again and again

  • Using VARCHAR(255) for everything. It hides the rules and makes indexes bigger than needed.
  • Storing dates as text, or numbers as text you compute with.
  • Using FLOAT for money.
  • Using INT for a table that will exceed 2 billion rows (or BIGINT for a lookup table with ten rows).
  • A phone number as INT. It loses leading zeros and cannot store +91.
  • One huge TEXT column for structured data that you later need to search.
  • A table with 100 columns. It often means that several things are mixed together (Normalization).
  • Different types for the same fact. users.id as INT and orders.user_id as BIGINT breaks index use in joins and foreign keys. A foreign key column must match the referenced key’s type exactly (and sign).
query.sql
SQL
CREATE TABLE users (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL);
CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users (id)
);
Output
OUTPUT
ERROR 3780 (HY000): Referencing column 'user_id' and referenced column 'id' in foreign key constraint 'posts_ibfk_1' are incompatible.

The foreign key was refused because BIGINT (signed) does not match INT UNSIGNED. Make the types identical:

query.sql
SQL
CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users (id)
);
SHOW TABLES;
Output
OUTPUT
+-----------------+
| Tables_in_learn |
+-----------------+
| posts           |
| users           |
+-----------------+
2 rows in set

Changing your mind later

Changing a column’s type later rebuilds the table, which can take a long time on big tables (Online DDL). The earlier you pick the right type, the cheaper it is. Spend ten minutes on types before you create a table that will hold millions of rows.

Exercise: design the columns

You are designing a members table for a gym. Choose a type for: member id, full name, phone number, date of birth, monthly fee in rupees with paise, is_active flag and joined_at moment. Write the CREATE TABLE statement.

Show a possible solution
query.sql
SQL
CREATE TABLE members (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    full_name VARCHAR(100) NOT NULL,
    phone VARCHAR(20) NOT NULL,
    date_of_birth DATE NOT NULL,
    monthly_fee DECIMAL(8,2) NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
DESCRIBE members;
Output
OUTPUT
+---------------+--------------+------+-----+-------------------+-------------------+
| Field         | Type         | Null | Key | Default           | Extra             |
+---------------+--------------+------+-----+-------------------+-------------------+
| id            | int unsigned | NO   | PRI | NULL              | auto_increment    |
| full_name     | varchar(100) | NO   |     | NULL              |                   |
| phone         | varchar(20)  | NO   |     | NULL              |                   |
| date_of_birth | date         | NO   |     | NULL              |                   |
| monthly_fee   | decimal(8,2) | NO   |     | NULL              |                   |
| is_active     | tinyint(1)   | NO   |     | 1                 |                   |
| joined_at     | datetime     | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+---------------+--------------+------+-----+-------------------+-------------------+
7 rows in set