How to Create Tables in MySQL

How to Create Tables in MySQL

Tables are where I actually start caring about database design, because this is the step where data types, keys, and constraints turn a blank schema into something that can actually enforce data integrity. I’ve designed hundreds of tables over the years, some well thought out and some I had to fix later, so in this guide I’m sharing everything I wish someone had told me before I created my first CREATE TABLE statement.

Understanding Tables Within MySQL’s Architecture

A table in MySQL is the physical and logical unit where rows of data are actually stored, indexed, and queried. With the InnoDB storage engine (my default choice), each table is backed by:

graph TD
    A[CREATE TABLE Statement] --> B[SQL Layer Parses DDL]
    B --> C[Data Dictionary Updated]
    C --> D[InnoDB Allocates Tablespace .ibd file]
    D --> E[Clustered Index Built on Primary Key]
    E --> F[Table Ready for Rows]

Step 1: Choosing the Database

USE shop_db;

Step 2: A Basic CREATE TABLE Statement

CREATE TABLE customers (
    id INT AUTO_INCREMENT PRIMARY KEY,
    full_name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Output:

Query OK, 0 rows affected (0.03 sec)

I break this down piece by piece whenever I’m teaching someone new:

Choosing the Right Data Types

Data TypeStorage SizeWhen I Use It
TINYINT1 byteBoolean flags, small enums (0-255)
INT4 bytesStandard integer IDs, counts
BIGINT8 bytesHigh-volume auto-increment IDs, large counters
DECIMAL(p,s)VariableMoney and financial values — never FLOAT for currency
VARCHAR(n)n+1 or n+2 bytesVariable-length text with a known max length
TEXTVariable, up to 64KBLong-form content like descriptions
DATETIME8 bytesTimestamps without timezone conversion
TIMESTAMP4 bytesTimestamps that auto-convert with timezone, auto-update support
JSONVariableSemi-structured data, since MySQL 5.7.8

I never use FLOAT or DOUBLE for monetary values because of floating-point rounding errors — I always reach for DECIMAL(10,2) instead.

Step 3: A More Advanced Table With Constraints

CREATE TABLE orders (
    order_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_id INT NOT NULL,
    order_total DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    status ENUM('pending', 'shipped', 'delivered', 'cancelled') NOT NULL DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_customer
        FOREIGN KEY (customer_id) REFERENCES customers(id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
) ENGINE=InnoDB;

I want to point out a few important choices here:

Verifying Table Structure

DESCRIBE customers;

Output:

+------------+--------------+------+-----+-------------------+-------------------+
| Field      | Type         | Null | Key | Default           | Extra             |
+------------+--------------+------+-----+-------------------+-------------------+
| id         | int          | NO   | PRI | NULL               | auto_increment    |
| full_name  | varchar(100) | NO   |     | NULL               |                    |
| email      | varchar(150) | NO   | UNI | NULL               |                    |
| created_at | timestamp    | YES  |     | CURRENT_TIMESTAMP  |                    |
+------------+--------------+------+-----+-------------------+-------------------+

I also frequently use:

SHOW CREATE TABLE customers\G

This gives me the exact DDL MySQL would use to recreate the table, which I find invaluable when documenting schema or debugging discrepancies between environments.

Table Relationships Diagram

erDiagram
    CUSTOMERS ||--o{ ORDERS : places
    CUSTOMERS {
        int id PK
        varchar full_name
        varchar email
    }
    ORDERS {
        bigint order_id PK
        int customer_id FK
        decimal order_total
        enum status
    }

Choosing Between Storage Engines at Table Creation Time

EngineTransactionsForeign KeysFull-Text SearchBest For
InnoDBYesYesYes (since 5.6)Almost everything I build today
MyISAMNoNoYesRare legacy read-heavy reporting tables
MemoryNoNoNoSession data, ephemeral caching tables

A Real-World Scenario: Designing an E-Commerce Schema

When I designed the schema for a client’s online store, I structured the core tables like this:

CREATE TABLE products (
    product_id INT AUTO_INCREMENT PRIMARY KEY,
    sku VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(200) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock_quantity INT NOT NULL DEFAULT 0,
    category_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_category (category_id)
) ENGINE=InnoDB;

CREATE TABLE order_items (
    order_item_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
) ENGINE=InnoDB;

I added INDEX idx_category (category_id) at table creation time because I already knew from the requirements that category-based browsing would be one of the most frequent queries the storefront would run.

Altering a Table After Creation

Requirements change constantly in my experience, so I use ALTER TABLE regularly:

ALTER TABLE customers ADD COLUMN phone VARCHAR(20) AFTER email;
ALTER TABLE customers MODIFY COLUMN full_name VARCHAR(150) NOT NULL;
ALTER TABLE customers DROP COLUMN phone;

I’m always careful with ALTER TABLE on large production tables, since older MySQL versions would lock the entire table during certain operations. MySQL 8.0’s support for instant DDL (ALGORITHM=INSTANT) has made adding columns dramatically faster in many cases, but I still verify with:

ALTER TABLE customers ADD COLUMN loyalty_points INT DEFAULT 0, ALGORITHM=INSTANT;

Security Considerations at Table Design Time

Troubleshooting Common Table Creation Issues

Issue: “Cannot add foreign key constraint”

ERROR 1215 (HY000): Cannot add foreign key constraint

I check three things every time this happens: matching data types between the foreign key and referenced column, matching character sets/collations for string columns, and that the referenced table uses InnoDB.

Issue: “Row size too large”

This happens when I pack too many large VARCHAR or TEXT columns into one table, exceeding InnoDB’s row size limits. I resolve it by moving large text fields into a related table or switching some columns to TEXT/BLOB, which are stored off-page.

Issue: Table already exists

CREATE TABLE IF NOT EXISTS customers (...);

Performance Best Practices for Table Design

Frequently Asked Questions

Q: What happens if I don’t define a primary key? A: InnoDB will use the first UNIQUE NOT NULL index it finds, or if none exists, it creates a hidden internal clustered index. I always define an explicit primary key to avoid this.

Q: Should I use INT or BIGINT for primary keys? A: For tables I expect to exceed roughly 2 billion rows, I use BIGINT. For everything else, INT is sufficient and saves storage.

Q: What’s the difference between CHAR and VARCHAR? A: CHAR is fixed-length and padded with spaces; VARCHAR is variable-length. I use CHAR only for genuinely fixed-length data like country codes.

Q: Can I add a foreign key after the table already has data? A: Yes, using ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY, but MySQL will validate existing data against the constraint and fail if there are orphaned rows.

Interview Questions I’ve Encountered

  1. What’s the difference between a clustered index and a secondary index in InnoDB?
  2. Why would you choose DECIMAL over FLOAT for financial data?
  3. Explain what happens internally when a table has no explicit primary key.
  4. How do foreign key constraints affect INSERT and DELETE performance?
  5. What is instant DDL in MySQL 8.0, and which operations support it?
  6. How would you design a schema to handle millions of order records efficiently?

Summary and Key Takeaways

Creating tables in MySQL is where database design decisions start to have real, lasting consequences. I always think carefully about data types, primary keys, foreign key relationships, and indexing strategy before I run my first CREATE TABLE statement, because retrofitting these decisions later on a live production table is far more painful than getting them right from the start.

Key takeaways:

References

Exit mobile version