Home MySQL
Post
Cancel
MySQL | SEG

MySQL

This post builds on SQL fundamentals. If you need a refresher on SQL basics, check out our post on Structured Query Language first!

What is MySQL?

MySQL is the world’s most popular open-source relational database management system (RDBMS). Created in 1995 by MySQL AB (now owned by Oracle Corporation), it has become the go-to database for web applications and is a core component of the famous LAMP stack (Linux, Apache, MySQL, PHP/Python/Perl).

If you’ve used WordPress, Joomla, Drupal, or countless other web applications, you’ve already interacted with MySQL, even if you didn’t know it. Companies like Facebook, Twitter, YouTube, Netflix, and Airbnb all rely on MySQL at scale.

MySQL’s widespread adoption isn’t an accident. Here’s why millions of developers choose MySQL:

  1. Ease of Use - Simple to install, configure, and get running within minutes
  2. Speed - Optimized for read-heavy workloads, making it perfect for web applications
  3. Reliability - Battle-tested for over 25 years in production environments
  4. Widespread Hosting Support - Available on virtually every hosting provider
  5. Strong Community - Massive ecosystem of tools, tutorials, and community support
  6. Free and Open Source - Available under GPL license (with commercial support options)

MySQL is an excellent choice for your first database project. Its simplicity and abundance of learning resources make it ideal for beginners, while its performance and reliability make it production-ready.

Core MySQL Architecture

Understanding MySQL’s architecture helps you write better applications and troubleshoot issues effectively.

MySQL Server Structure

MySQL uses a layered architecture that separates the query processing from storage:

flowchart TB
    Client[Client Applications<br/>Java, Python, PHP, etc.]
    
    subgraph MySQLServer["MySQL Server"]
        direction TB
        ConnLayer[Connection Layer<br/>Authentication, Thread Management]
        
        subgraph SQLLayer["SQL Layer"]
            Parser[Query Parser]
            Optimizer[Query Optimizer]
            Cache[Query Cache<br/>Deprecated in 8.0+]
        end
        
        subgraph StorageLayer["Storage Engine Layer"]
            InnoDB[InnoDB Engine<br/>Default, Transactional]
            MyISAM[MyISAM Engine<br/>Legacy, Non-transactional]
            Memory[Memory Engine<br/>In-memory tables]
        end
    end
    
    Disk[Disk Storage<br/>Data Files, Logs, Indexes]
    
    Client --> ConnLayer
    ConnLayer --> Parser
    Parser --> Optimizer
    Optimizer --> InnoDB & MyISAM & Memory
    InnoDB & MyISAM --> Disk
    Memory -.->|Temporary| Disk
    
    style MySQLServer fill:#00758F,color:#fff
    style SQLLayer fill:#F29111,color:#fff
    style StorageLayer fill:#00758F,color:#fff
Connection Layer - Handles client connections, authentication, and security. When your application connects to MySQL, this layer authenticates the user, manages the connection thread, and enforces security permissions. MySQL supports multiple simultaneous connections, each handled by a separate thread.
SQL Layer - Processes and optimizes SQL queries. This layer parses your SQL statements, checks for syntax errors, optimizes query execution plans, and determines the most efficient way to retrieve data. The query cache (removed in MySQL 8.0) used to store frequently-used query results for faster retrieval.
Storage Engine Layer - The pluggable layer that actually stores and retrieves data. This is what makes MySQL unique: you can choose different storage engines for different tables based on your needs. InnoDB (the default) provides ACID transactions, foreign keys, and crash recovery. MyISAM offers simpler, faster storage without transactions. Memory stores data entirely in RAM for maximum speed.

Database Organization

Like other relational databases, MySQL organizes data hierarchically:

flowchart TB
    subgraph Server["MySQL Server<br/>localhost:3306"]
        subgraph DB1["Database: webshop"]
            T1[Table: customers<br/>InnoDB]
            T2[Table: orders<br/>InnoDB]
            T3[Table: products<br/>InnoDB]
            I1[Indexes]
            V1[Views]
        end
        subgraph DB2["Database: analytics"]
            T4[Table: page_views<br/>MyISAM]
            T5[Table: sessions<br/>Memory]
        end
        SysDB[System Databases<br/>mysql, information_schema,<br/>performance_schema]
    end
    
    style Server fill:#00758F,color:#fff
    style DB1 fill:#F29111,color:#000
    style DB2 fill:#F29111,color:#000
    style SysDB fill:#E48F00,color:#fff

Unlike PostgreSQL, MySQL doesn’t have schemas within databases. Each database is a separate namespace, so you organize objects at the database level rather than using schemas.

MySQL vs PostgreSQL: Choosing the Right Database

Both MySQL and PostgreSQL are excellent databases, but they have different strengths. As a junior developer, understanding these differences helps you make informed architecture decisions.

AspectMySQLPostgreSQL
Primary FocusSpeed and simplicityFeature richness and standards compliance
Best ForRead-heavy web applicationsComplex queries and data integrity
Read PerformanceExcellent (optimized for reads)Very good
Write PerformanceVery goodExcellent (better concurrency)
Ease of SetupExtremely easySlightly more complex
Hosting SupportNearly universalVery widespread
Advanced FeaturesBasic (improving in 8.0+)Extensive (JSON, arrays, custom types)
ReplicationBuilt-in, easy to configureBuilt-in, more configuration options
ACID ComplianceYes (with InnoDB)Yes (by design)
Full-Text SearchBasicAdvanced with multiple languages
JSON SupportAdded in 5.7Native, with indexing and operators
Learning CurveGentleModerate
CommunityMassiveLarge and active

When to Choose MySQL

Use MySQL when:

  • Building a standard web application (blog, e-commerce, CMS)
  • Your workload is heavily read-focused (more reads than writes)
  • You need maximum hosting compatibility
  • You want the simplest possible setup
  • You’re working with legacy PHP applications
  • You need exceptional replication for read scaling
  • Your queries are relatively simple

Example use cases:

  • WordPress blog or website
  • E-commerce platform (Magento, WooCommerce)
  • Content management system
  • Social media application with read-heavy patterns
  • Mobile app backend with simple data models

When to Choose PostgreSQL

Use PostgreSQL when:

  • Your application requires complex queries and joins
  • Data integrity is absolutely critical (financial, healthcare)
  • You need advanced data types (arrays, JSON, geometric types)
  • You’re doing complex analytical queries
  • You need better handling of concurrent writes
  • You want maximum SQL standards compliance
  • You need advanced full-text search capabilities

Example use cases:

  • Financial applications with complex transactions
  • Data analytics platforms
  • GIS applications (with PostGIS extension)
  • Applications requiring complex business logic in the database
  • Systems with heavy write concurrency

The Real Answer: Both Are Excellent Choices

The truth is, for 90% of web applications, both MySQL and PostgreSQL will work perfectly well. The “MySQL vs PostgreSQL” debate is often overblown. Here’s the practical advice:

  • Use MySQL if you’re just starting out, building a standard web app, or working with existing PHP/MySQL hosting
  • Use PostgreSQL if you’re building something more complex, need advanced features, or work in a team that prefers it

Many successful companies use both: MySQL for simple, read-heavy services and PostgreSQL for complex, transactional systems.

The best database is the one you know well and can maintain effectively.

Storage Engines: MySQL’s Superpower

One of MySQL’s unique features is its pluggable storage engine architecture. Different tables in the same database can use different engines optimized for different use cases.

InnoDB - The Default Choice

InnoDB is MySQL’s default storage engine since version 5.5 and the right choice for 99% of applications.

Key Features:

  • ACID Transactions - Full transaction support with rollback
  • Foreign Key Constraints - Enforces referential integrity
  • Crash Recovery - Automatic recovery after unexpected shutdown
  • Row-Level Locking - Better concurrency for writes
  • Multi-Version Concurrency Control (MVCC) - Readers don’t block writers
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Tables are InnoDB by default in modern MySQL
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10, 2),
    status VARCHAR(20) DEFAULT 'pending',
    
    -- Foreign key constraint (only works with InnoDB)
    FOREIGN KEY (customer_id) REFERENCES customers(id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE=InnoDB;

Always use InnoDB unless you have a specific reason not to. It’s the modern, reliable, and feature-complete choice.

MyISAM - The Legacy Engine

MyISAM was MySQL’s default engine before InnoDB became mature. It’s now considered legacy but you might encounter it in older applications.

Characteristics:

  • No Transactions - No COMMIT/ROLLBACK support
  • No Foreign Keys - Cannot enforce referential integrity
  • Full-Text Search - Better full-text indexing (before InnoDB added it)
  • Table-Level Locking - Simpler but less concurrent
  • Smaller Disk Footprint - More compact storage
1
2
3
4
5
6
-- Specifying MyISAM explicitly (rarely needed)
CREATE TABLE search_index (
    id INT PRIMARY KEY AUTO_INCREMENT,
    content TEXT,
    FULLTEXT INDEX (content)
) ENGINE=MyISAM;

Avoid MyISAM for new projects. It lacks transaction support and crash recovery, making it unsuitable for modern applications.

Memory (HEAP) - Temporary Speed

The Memory engine stores tables entirely in RAM for maximum speed. Data is lost when the server restarts.

Use Cases:

  • Temporary calculation tables
  • Session data
  • Caching results
  • Intermediate query results
1
2
3
4
5
6
7
-- Memory table for session management
CREATE TABLE active_sessions (
    session_id VARCHAR(64) PRIMARY KEY,
    user_id INT NOT NULL,
    last_activity TIMESTAMP,
    data TEXT
) ENGINE=MEMORY;

Storage Engine Comparison

flowchart TD
    Start[Need to choose a storage engine?]
    
    Transactions{Need ACID transactions?}
    Temporary{Storing temporary or volatile data?}
    Legacy{Working with legacy MyISAM?}
    
    InnoDB[InnoDB Engine - Modern, Full-Featured]
    Memory[Memory Engine - RAM-based, Fast]
    MyISAM[MyISAM Engine - Legacy, Limited]
    Convert[Convert to InnoDB - Recommended]
    
    Start --> Transactions
    Transactions -->|Yes| InnoDB
    Transactions -->|No| Temporary
    Temporary -->|Yes| Memory
    Temporary -->|No| Legacy
    Legacy -->|Yes| Convert
    Legacy -->|No| InnoDB

Essential MySQL Operations

Let’s dive into practical MySQL usage with examples you’ll use every day as a developer.

Database and Table Management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
-- Create a new database
CREATE DATABASE ecommerce
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

-- Use the database
USE ecommerce;

-- Create a customers table with common patterns
CREATE TABLE customers (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) NOT NULL UNIQUE,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_email (email),
    INDEX idx_name (last_name, first_name)
) ENGINE=InnoDB;

-- Create orders table with foreign key
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
    total_amount DECIMAL(10, 2) NOT NULL,
    
    FOREIGN KEY (customer_id) REFERENCES customers(id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE,
    INDEX idx_customer (customer_id),
    INDEX idx_status (status),
    INDEX idx_order_date (order_date)
) ENGINE=InnoDB;

-- Create order items table (many-to-many relationship)
CREATE TABLE order_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    product_name VARCHAR(255) NOT NULL,
    quantity INT NOT NULL DEFAULT 1,
    unit_price DECIMAL(10, 2) NOT NULL,
    
    FOREIGN KEY (order_id) REFERENCES orders(id)
        ON DELETE CASCADE,
    INDEX idx_order (order_id)
) ENGINE=InnoDB;

Always use utf8mb4 character set, not utf8. MySQL’s utf8 encoding is actually a limited 3-byte implementation that can’t store emoji and some special characters. utf8mb4 is the true UTF-8 implementation.

CRUD Operations with MySQL-Specific Features

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- INSERT: Add new customers
INSERT INTO customers (email, first_name, last_name) VALUES
    ('john.doe@example.com', 'John', 'Doe'),
    ('jane.smith@example.com', 'Jane', 'Smith');

-- INSERT with ON DUPLICATE KEY UPDATE (MySQL-specific)
-- Updates if the email already exists (unique key violation)
INSERT INTO customers (email, first_name, last_name)
VALUES ('john.doe@example.com', 'John', 'Doe')
ON DUPLICATE KEY UPDATE
    first_name = VALUES(first_name),
    last_name = VALUES(last_name);

-- SELECT: Basic queries
SELECT * FROM customers WHERE last_name = 'Doe';

-- SELECT with JOIN
SELECT 
    c.first_name,
    c.last_name,
    o.id AS order_id,
    o.order_date,
    o.total_amount
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
WHERE o.status = 'pending'
ORDER BY o.order_date DESC;

-- UPDATE: Modify existing records
UPDATE orders
SET status = 'processing'
WHERE status = 'pending' 
  AND order_date < DATE_SUB(NOW(), INTERVAL 1 HOUR);

-- DELETE: Remove records
DELETE FROM orders
WHERE status = 'cancelled' 
  AND order_date < DATE_SUB(NOW(), INTERVAL 90 DAY);

Transactions in MySQL

Transactions ensure data consistency by grouping multiple operations into a single atomic unit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- Start a transaction
START TRANSACTION;

-- Insert an order
INSERT INTO orders (customer_id, total_amount, status)
VALUES (1, 150.00, 'pending');

-- Get the auto-generated order ID
SET @order_id = LAST_INSERT_ID();

-- Insert order items
INSERT INTO order_items (order_id, product_name, quantity, unit_price)
VALUES 
    (@order_id, 'Laptop', 1, 100.00),
    (@order_id, 'Mouse', 2, 25.00);

-- Commit if everything succeeded
COMMIT;

-- Or rollback if there was an error
-- ROLLBACK;

Always wrap related operations in transactions. If one operation fails, the entire transaction rolls back, keeping your data consistent.

Advanced Queries

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- Aggregation: Calculate statistics
SELECT 
    c.id,
    c.first_name,
    c.last_name,
    COUNT(o.id) AS total_orders,
    SUM(o.total_amount) AS total_spent,
    AVG(o.total_amount) AS avg_order_value
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.first_name, c.last_name
HAVING total_spent > 500.00
ORDER BY total_spent DESC;

-- Subqueries: Find customers with no orders
SELECT first_name, last_name, email
FROM customers
WHERE id NOT IN (
    SELECT DISTINCT customer_id FROM orders
);

-- Window Functions (MySQL 8.0+): Rank orders by customer
SELECT 
    customer_id,
    order_date,
    total_amount,
    ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS order_rank
FROM orders;

-- Common Table Expressions (MySQL 8.0+)
WITH customer_stats AS (
    SELECT 
        customer_id,
        COUNT(*) AS order_count,
        SUM(total_amount) AS total_spent
    FROM orders
    GROUP BY customer_id
)
SELECT 
    c.first_name,
    c.last_name,
    cs.order_count,
    cs.total_spent
FROM customers c
JOIN customer_stats cs ON c.id = cs.customer_id
WHERE cs.total_spent > 1000.00;

Working with MySQL from Java

Most real-world applications interact with MySQL programmatically. Let’s explore MySQL integration with Java using JDBC.

Setting Up MySQL Connector

First, add the MySQL JDBC driver to your project. For Maven:

1
2
3
4
5
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.2.0</version>
</dependency>

For Gradle:

1
implementation 'com.mysql:mysql-connector-j:8.2.0'

Basic JDBC Connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.sql.*;

public class MySQLConnection {
    // Database connection parameters
    private static final String URL = "jdbc:mysql://localhost:3306/ecommerce";
    private static final String USER = "your_username";
    private static final String PASSWORD = "your_password";
    
    /**
     * Establishes a connection to the MySQL database.
     * 
     * @return Connection object
     * @throws SQLException if connection fails
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    
    public static void main(String[] args) {
        // Try-with-resources ensures connection is closed automatically
        try (Connection conn = getConnection()) {
            System.out.println("Connected to MySQL database successfully!");
            
            // Get database metadata
            DatabaseMetaData metaData = conn.getMetaData();
            System.out.println("Database: " + metaData.getDatabaseProductName());
            System.out.println("Version: " + metaData.getDatabaseProductVersion());
            
        } catch (SQLException e) {
            System.err.println("Connection failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

CRUD Operations with JDBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class CustomerDAO {
    
    /**
     * Represents a customer entity
     */
    public static class Customer {
        private int id;
        private String email;
        private String firstName;
        private String lastName;
        
        // Constructors
        public Customer() {}
        
        public Customer(String email, String firstName, String lastName) {
            this.email = email;
            this.firstName = firstName;
            this.lastName = lastName;
        }
        
        // Getters and setters
        public int getId() { return id; }
        public void setId(int id) { this.id = id; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
        public String getFirstName() { return firstName; }
        public void setFirstName(String firstName) { this.firstName = firstName; }
        public String getLastName() { return lastName; }
        public void setLastName(String lastName) { this.lastName = lastName; }
        
        @Override
        public String toString() {
            return String.format("Customer[id=%d, name=%s %s, email=%s]",
                id, firstName, lastName, email);
        }
    }
    
    /**
     * Inserts a new customer into the database.
     * 
     * @param customer Customer object to insert
     * @return Generated customer ID
     */
    public static int createCustomer(Customer customer) throws SQLException {
        String sql = "INSERT INTO customers (email, first_name, last_name) VALUES (?, ?, ?)";
        
        try (Connection conn = MySQLConnection.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            
            // Set parameters (prevents SQL injection)
            stmt.setString(1, customer.getEmail());
            stmt.setString(2, customer.getFirstName());
            stmt.setString(3, customer.getLastName());
            
            // Execute the insert
            int affectedRows = stmt.executeUpdate();
            
            if (affectedRows == 0) {
                throw new SQLException("Creating customer failed, no rows affected.");
            }
            
            // Retrieve the auto-generated ID
            try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
                if (generatedKeys.next()) {
                    int customerId = generatedKeys.getInt(1);
                    customer.setId(customerId);
                    return customerId;
                } else {
                    throw new SQLException("Creating customer failed, no ID obtained.");
                }
            }
        }
    }
    
    /**
     * Retrieves a customer by ID.
     * 
     * @param id Customer ID
     * @return Customer object or null if not found
     */
    public static Customer getCustomerById(int id) throws SQLException {
        String sql = "SELECT id, email, first_name, last_name FROM customers WHERE id = ?";
        
        try (Connection conn = MySQLConnection.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            stmt.setInt(1, id);
            
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    Customer customer = new Customer();
                    customer.setId(rs.getInt("id"));
                    customer.setEmail(rs.getString("email"));
                    customer.setFirstName(rs.getString("first_name"));
                    customer.setLastName(rs.getString("last_name"));
                    return customer;
                }
            }
        }
        return null;
    }
    
    /**
     * Retrieves all customers.
     * 
     * @return List of all customers
     */
    public static List<Customer> getAllCustomers() throws SQLException {
        String sql = "SELECT id, email, first_name, last_name FROM customers ORDER BY last_name";
        List<Customer> customers = new ArrayList<>();
        
        try (Connection conn = MySQLConnection.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            
            while (rs.next()) {
                Customer customer = new Customer();
                customer.setId(rs.getInt("id"));
                customer.setEmail(rs.getString("email"));
                customer.setFirstName(rs.getString("first_name"));
                customer.setLastName(rs.getString("last_name"));
                customers.add(customer);
            }
        }
        return customers;
    }
    
    /**
     * Updates an existing customer.
     * 
     * @param customer Customer object with updated data
     * @return true if customer was updated, false if not found
     */
    public static boolean updateCustomer(Customer customer) throws SQLException {
        String sql = "UPDATE customers SET email = ?, first_name = ?, last_name = ? WHERE id = ?";
        
        try (Connection conn = MySQLConnection.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            stmt.setString(1, customer.getEmail());
            stmt.setString(2, customer.getFirstName());
            stmt.setString(3, customer.getLastName());
            stmt.setInt(4, customer.getId());
            
            int affectedRows = stmt.executeUpdate();
            return affectedRows > 0;
        }
    }
    
    /**
     * Deletes a customer by ID.
     * 
     * @param id Customer ID
     * @return true if customer was deleted, false if not found
     */
    public static boolean deleteCustomer(int id) throws SQLException {
        String sql = "DELETE FROM customers WHERE id = ?";
        
        try (Connection conn = MySQLConnection.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            stmt.setInt(1, id);
            int affectedRows = stmt.executeUpdate();
            return affectedRows > 0;
        }
    }
    
    /**
     * Example usage
     */
    public static void main(String[] args) {
        try {
            // Create a new customer
            Customer customer = new Customer(
                "alice.johnson@example.com",
                "Alice",
                "Johnson"
            );
            int customerId = createCustomer(customer);
            System.out.println("Created customer with ID: " + customerId);
            
            // Read the customer
            Customer retrieved = getCustomerById(customerId);
            System.out.println("Retrieved: " + retrieved);
            
            // Update the customer
            retrieved.setEmail("alice.j@example.com");
            updateCustomer(retrieved);
            System.out.println("Updated customer email");
            
            // List all customers
            List<Customer> allCustomers = getAllCustomers();
            System.out.println("\nAll customers:");
            allCustomers.forEach(System.out::println);
            
            // Delete the customer
            deleteCustomer(customerId);
            System.out.println("\nDeleted customer with ID: " + customerId);
            
        } catch (SQLException e) {
            System.err.println("Database error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Always use PreparedStatement instead of Statement to prevent SQL injection attacks. Never concatenate user input directly into SQL queries!

Transaction Management in Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import java.sql.*;

public class OrderService {
    
    /**
     * Creates an order with multiple items in a single transaction.
     * If any operation fails, everything is rolled back.
     */
    public static int createOrderWithItems(
        int customerId,
        List<OrderItem> items
    ) throws SQLException {
        
        Connection conn = null;
        PreparedStatement orderStmt = null;
        PreparedStatement itemStmt = null;
        
        try {
            // Get connection and disable auto-commit
            conn = MySQLConnection.getConnection();
            conn.setAutoCommit(false);
            
            // Insert the order
            String orderSql = "INSERT INTO orders (customer_id, total_amount, status) VALUES (?, ?, ?)";
            orderStmt = conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS);
            
            double totalAmount = items.stream()
                .mapToDouble(item -> item.quantity * item.unitPrice)
                .sum();
            
            orderStmt.setInt(1, customerId);
            orderStmt.setDouble(2, totalAmount);
            orderStmt.setString(3, "pending");
            orderStmt.executeUpdate();
            
            // Get the generated order ID
            int orderId;
            try (ResultSet rs = orderStmt.getGeneratedKeys()) {
                if (rs.next()) {
                    orderId = rs.getInt(1);
                } else {
                    throw new SQLException("Failed to get order ID");
                }
            }
            
            // Insert order items
            String itemSql = "INSERT INTO order_items (order_id, product_name, quantity, unit_price) VALUES (?, ?, ?, ?)";
            itemStmt = conn.prepareStatement(itemSql);
            
            for (OrderItem item : items) {
                itemStmt.setInt(1, orderId);
                itemStmt.setString(2, item.productName);
                itemStmt.setInt(3, item.quantity);
                itemStmt.setDouble(4, item.unitPrice);
                itemStmt.addBatch();
            }
            
            // Execute all item inserts
            itemStmt.executeBatch();
            
            // Commit the transaction
            conn.commit();
            System.out.println("Order created successfully with ID: " + orderId);
            return orderId;
            
        } catch (SQLException e) {
            // Rollback on any error
            if (conn != null) {
                try {
                    conn.rollback();
                    System.err.println("Transaction rolled back due to error: " + e.getMessage());
                } catch (SQLException rollbackEx) {
                    System.err.println("Rollback failed: " + rollbackEx.getMessage());
                }
            }
            throw e;
            
        } finally {
            // Clean up resources
            if (itemStmt != null) itemStmt.close();
            if (orderStmt != null) orderStmt.close();
            if (conn != null) {
                conn.setAutoCommit(true);  // Restore auto-commit
                conn.close();
            }
        }
    }
    
    /**
     * Helper class for order items
     */
    public static class OrderItem {
        String productName;
        int quantity;
        double unitPrice;
        
        public OrderItem(String productName, int quantity, double unitPrice) {
            this.productName = productName;
            this.quantity = quantity;
            this.unitPrice = unitPrice;
        }
    }
}

Performance Considerations for Beginners

Writing queries is one thing; writing fast queries is another. Here are essential performance tips for MySQL.

Indexing Basics

Indexes are like a book’s index, they help MySQL find data quickly without scanning every row.

1
2
3
4
5
6
7
8
9
10
11
-- Create an index on frequently queried columns
CREATE INDEX idx_customer_email ON customers(email);

-- Composite index for multiple columns
CREATE INDEX idx_order_status_date ON orders(status, order_date);

-- Check existing indexes
SHOW INDEXES FROM orders;

-- Analyze query performance
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND order_date > '2026-01-01';

Create indexes on columns used in WHERE, JOIN, and ORDER BY clauses. But don’t over-index—each index slows down INSERT/UPDATE operations.

Query Optimization Tips

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- ❌ BAD: SELECT * returns unnecessary data
SELECT * FROM customers;

-- ✅ GOOD: Only select needed columns
SELECT id, first_name, last_name FROM customers;

-- ❌ BAD: Function on indexed column prevents index use
SELECT * FROM orders WHERE DATE(order_date) = '2026-01-01';

-- ✅ GOOD: Keep indexed column unchanged
SELECT * FROM orders WHERE order_date >= '2026-01-01' AND order_date < '2026-01-02';

-- ❌ BAD: NOT IN with subquery is slow
SELECT * FROM customers WHERE id NOT IN (SELECT customer_id FROM orders);

-- ✅ GOOD: LEFT JOIN with NULL check is faster
SELECT c.* FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.customer_id IS NULL;

-- Use LIMIT for pagination
SELECT * FROM orders ORDER BY order_date DESC LIMIT 20 OFFSET 0;

Connection Pooling

Opening database connections is expensive. Use connection pooling in production applications:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class DatabasePool {
    private static HikariDataSource dataSource;
    
    static {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/ecommerce");
        config.setUsername("your_username");
        config.setPassword("your_password");
        
        // Pool configuration
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);  // 30 seconds
        config.setIdleTimeout(600000);       // 10 minutes
        config.setMaxLifetime(1800000);      // 30 minutes
        
        dataSource = new HikariDataSource(config);
    }
    
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    
    public static void closePool() {
        if (dataSource != null) {
            dataSource.close();
        }
    }
}

Add HikariCP to your dependencies:

1
2
3
4
5
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.1.0</version>
</dependency>

Common Use Cases

MySQL excels in specific scenarios. Understanding these helps you choose the right tool for your project.

1. Web Content Management

Perfect for: WordPress, Drupal, Joomla, and custom CMS applications

Why MySQL?

  • Read-heavy workload (more page views than edits)
  • Simple relational structure
  • Universal hosting support
  • Mature ecosystem of tools and plugins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Typical blog schema
CREATE TABLE posts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    slug VARCHAR(255) NOT NULL UNIQUE,
    content TEXT,
    author_id INT NOT NULL,
    published_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_slug (slug),
    INDEX idx_author (author_id),
    INDEX idx_published (published_at)
) ENGINE=InnoDB;

2. E-Commerce Platforms

Perfect for: Online stores, marketplaces, shopping carts

Why MySQL?

  • Fast product catalog queries
  • Simple transactional requirements
  • Good replication for read scaling
  • Integration with popular platforms (Magento, WooCommerce)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Product catalog with categories
CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    sku VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    stock_quantity INT DEFAULT 0,
    category_id INT,
    is_active BOOLEAN DEFAULT true,
    
    INDEX idx_sku (sku),
    INDEX idx_category (category_id),
    INDEX idx_active (is_active),
    FULLTEXT INDEX idx_search (name, description)
) ENGINE=InnoDB;

3. User Authentication Systems

Perfect for: Login systems, session management, user profiles

Why MySQL?

  • Simple schema with straightforward relationships
  • Fast lookups by username/email
  • Reliable transaction support for critical operations
1
2
3
4
5
6
7
8
9
10
11
12
13
-- User authentication table
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    last_login TIMESTAMP NULL,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_username (username),
    INDEX idx_email (email)
) ENGINE=InnoDB;

4. Logging and Analytics

Perfect for: Application logs, event tracking, basic analytics

Why MySQL?

  • Fast inserts for high-volume logging
  • Good aggregation performance
  • Partitioning support for time-series data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- Application log table with partitioning
CREATE TABLE application_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    log_level ENUM('DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'),
    message TEXT,
    user_id INT,
    ip_address VARCHAR(45),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_level (log_level),
    INDEX idx_user (user_id),
    INDEX idx_created (created_at)
) ENGINE=InnoDB
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
    PARTITION p_2026_01 VALUES LESS THAN (UNIX_TIMESTAMP('2026-02-01')),
    PARTITION p_2026_02 VALUES LESS THAN (UNIX_TIMESTAMP('2026-03-01')),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

Best Practices for Junior Developers

Following these practices will help you build reliable, maintainable MySQL applications.

1. Character Sets and Collations

1
2
3
4
5
6
7
8
9
-- ✅ ALWAYS use utf8mb4 (true UTF-8)
CREATE DATABASE myapp
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

-- Apply to tables too
CREATE TABLE users (
    name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2. Naming Conventions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- ✅ Use clear, consistent names
-- Tables: plural nouns (customers, orders, products)
-- Columns: singular, snake_case (first_name, created_at)
-- Indexes: idx_tablename_columnname
-- Foreign keys: fk_tablename_columnname

CREATE TABLE order_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    
    INDEX idx_order_items_order_id (order_id),
    INDEX idx_order_items_product_id (product_id),
    
    CONSTRAINT fk_order_items_order_id 
        FOREIGN KEY (order_id) REFERENCES orders(id),
    CONSTRAINT fk_order_items_product_id 
        FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB;

3. Timestamps and Audit Columns

1
2
3
4
5
6
7
-- ✅ Include created_at and updated_at on most tables
CREATE TABLE example (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

4. Foreign Key Constraints

1
2
3
4
5
6
7
8
9
-- ✅ Use foreign keys for referential integrity
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    
    FOREIGN KEY (customer_id) REFERENCES customers(id)
        ON DELETE RESTRICT      -- Prevent deleting customers with orders
        ON UPDATE CASCADE       -- Update order if customer ID changes
) ENGINE=InnoDB;

5. Backup and Recovery

Never run a production MySQL database without regular backups. Data loss is not a matter of “if” but “when.”

1
2
3
4
5
6
7
8
9
10
11
# Backup a database
mysqldump -u username -p database_name > backup.sql

# Backup with compression
mysqldump -u username -p database_name | gzip > backup.sql.gz

# Restore a database
mysql -u username -p database_name < backup.sql

# Restore from compressed backup
gunzip < backup.sql.gz | mysql -u username -p database_name

Common Mistakes to Avoid

Learn from these common pitfalls that trip up junior developers.

1. Not Using Prepared Statements

1
2
3
4
5
6
7
8
9
10
// ❌ DANGEROUS: SQL injection vulnerability
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

// ✅ SAFE: Use prepared statements
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();

2. Forgetting to Close Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ BAD: Resources might not be closed on exception
Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
// ... use results
conn.close();  // Never reached if exception occurs

// ✅ GOOD: Try-with-resources guarantees cleanup
try (Connection conn = getConnection();
     PreparedStatement stmt = conn.prepareStatement(sql);
     ResultSet rs = stmt.executeQuery()) {
    // ... use results
}  // Automatically closed

3. Over-Fetching Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ BAD: Loading all data into memory
String sql = "SELECT * FROM orders";  // Could be millions of rows!
List<Order> orders = new ArrayList<>();
try (ResultSet rs = stmt.executeQuery(sql)) {
    while (rs.next()) {
        orders.add(mapOrder(rs));
    }
}

// ✅ GOOD: Use pagination
String sql = "SELECT * FROM orders ORDER BY id LIMIT ? OFFSET ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, pageSize);
stmt.setInt(2, pageNumber * pageSize);

4. Ignoring Transactions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ BAD: Operations can be partially completed
stmt1.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
// If this fails, account 1 loses money but account 2 doesn't gain it!
stmt2.executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE id = 2");

// ✅ GOOD: Use transactions
conn.setAutoCommit(false);
try {
    stmt1.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    stmt2.executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    conn.commit();
} catch (SQLException e) {
    conn.rollback();
    throw e;
}

5. Using MyISAM Instead of InnoDB

1
2
3
4
5
-- ❌ BAD: MyISAM has no transaction support, crash recovery, or foreign keys
CREATE TABLE orders (...) ENGINE=MyISAM;

-- ✅ GOOD: Use InnoDB (it's the default anyway)
CREATE TABLE orders (...) ENGINE=InnoDB;

Next Steps in Your MySQL Journey

Congratulations! You now understand MySQL fundamentals and can build database-backed applications. Here’s how to continue learning:

Immediate Next Steps

  1. Build a Project - Create a complete application with MySQL backend
  2. Practice Writing Queries - Use platforms like LeetCode (Database section)
  3. Learn Query Optimization - Use EXPLAIN to understand query performance
  4. Explore Replication - Set up source-replica replication for scalability

Advanced Topics to Explore

  • Stored Procedures and Functions - Database-side business logic
  • Triggers - Automatic actions on data changes
  • Views - Virtual tables for complex queries
  • Partitioning - Splitting large tables for performance
  • Full-Text Search - Advanced text searching capabilities
  • JSON Support - Storing and querying JSON data (MySQL 5.7+)

Additional Resources

Conclusion

MySQL is an excellent choice for your database needs, especially when starting your software engineering journey. Its combination of simplicity, performance, and widespread adoption makes it ideal for learning database concepts and building real-world applications.

Remember these key takeaways:

  • Use InnoDB for all new tables (transactions, foreign keys, crash recovery)
  • Always use utf8mb4 character set, never plain utf8
  • Write secure code with prepared statements to prevent SQL injection
  • Think in transactions for operations that must complete together
  • Index strategically on columns used in WHERE, JOIN, and ORDER BY
  • Choose MySQL for read-heavy web applications with straightforward data models

MySQL will serve you well whether you’re building a personal blog, an e-commerce platform, or a complex web application. Master these fundamentals, and you’ll have a solid foundation for working with relational databases throughout your career.

The database is the heart of most applications. Learn it well, respect it, and it will serve you reliably for years to come.

Happy coding, and may your queries always return in milliseconds! 🚀

This post is licensed under CC BY 4.0 by the author.