Denormalization, Database Design Review, and Best Practices in MySQL
Course: MySQL Training
Topic: Denormalization, Database Design Review, and Best Practices
Course Link: MySQL Training Course
Demo Link: Register for a Course Demo
1. Introduction to Denormalization and Database Design Review
Database design is an essential part of building reliable, efficient, and maintainable applications. A well-designed MySQL database organizes data into appropriate tables, defines relationships between entities, enforces data integrity, and supports efficient queries.
Normalization is commonly used to reduce redundant data and prevent insertion, update, and deletion anomalies. However, highly normalized databases may require multiple joins to retrieve information for frequently used reports or application screens. In selected situations, these joins can increase query complexity and affect performance.
Denormalization is the intentional introduction of controlled data redundancy to improve query performance or simplify data retrieval. It should be applied only after understanding the workload, measuring performance, and considering the risks to data consistency.
Database design review is the systematic examination of a database schema, relationships, constraints, indexes, queries, security settings, and maintenance procedures to determine whether the design meets business and technical requirements.
Database design best practices help developers create databases that are accurate, secure, scalable, and easier to maintain as application requirements evolve.
Learning Objectives
- Understand the meaning and purpose of denormalization.
- Differentiate normalization from denormalization.
- Identify situations where denormalization may improve performance.
- Understand the risks of redundant data and how to control them.
- Review tables, keys, relationships, constraints, and indexes.
- Evaluate database queries and identify performance bottlenecks.
- Apply practical MySQL database design best practices.
- Perform a structured database design review.
2. Understanding Denormalization
2.1 Definition of Denormalization
Denormalization is a database design technique in which selected information is duplicated, precomputed, or stored in a form that reduces the work required to retrieve frequently requested data.
For example, an e-commerce application may store the customer's name in an order reporting table even though the customer name is already available in a separate customers table. This can reduce the need to join the customer table for every reporting query.
Denormalization does not mean abandoning database design principles. It means making a deliberate trade-off between read performance, storage requirements, data consistency, and write complexity.
2.2 Why Is Denormalization Used?
- Improve read performance: Reduce the number of joins or calculations needed by frequent queries.
- Simplify reporting: Provide commonly required information in a reporting-oriented structure.
- Reduce repeated computation: Store precomputed totals or summaries when recalculating them is expensive.
- Support analytical workloads: Organize data to support dashboards, aggregates, and business intelligence queries.
- Improve predictable query response: Reduce the work performed by specific, frequently executed read operations.
2.3 Example of a Denormalized Table
Suppose an online store has a normalized database with separate customers and orders tables.
Customers table:
| customer_id |
customer_name |
city |
| 1 |
Amit Sharma |
Mumbai |
| 2 |
Priya Verma |
Pune |
Orders table:
| order_id |
customer_id |
order_total |
| 101 |
1 |
2500.00 |
| 102 |
2 |
1800.00 |
To display order information with customer names, MySQL can join the two tables:
SELECT
o.order_id,
c.customer_name,
o.order_total
FROM orders AS o
JOIN customers AS c
ON o.customer_id = c.customer_id;
If a specific reporting workload repeatedly needs this information and the join is proven to be a bottleneck, the application might maintain a separate reporting table containing the order ID, customer name, and order total.
Important: Copying the customer name into an order record introduces a consistency responsibility. If the customer changes their name, the business must decide whether historical orders should retain the original name or reflect the current name.
3. Normalization vs. Denormalization
Normalization and denormalization serve different purposes. Normalization emphasizes structured storage and data integrity, while denormalization selectively duplicates or precomputes data to improve particular read workloads.
| Feature |
Normalization |
Denormalization |
| Primary goal |
Reduce unnecessary redundancy and anomalies |
Improve selected query performance |
| Data duplication |
Generally minimized |
Introduced deliberately where useful |
| Storage usage |
Often lower for duplicated attributes |
May require more storage |
| Read queries |
May require more joins |
May require fewer joins or calculations |
| Write operations |
Often update a fact in one authoritative place |
May require synchronizing duplicated values |
| Consistency management |
Usually simpler for normalized attributes |
Requires explicit maintenance rules |
| Typical use |
Transactional applications and operational systems |
Selected reports, summaries, caches, and analytical workloads |
3.1 Example of Normalized Data
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
customer_name VARCHAR(100) NOT NULL,
city VARCHAR(100)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_total DECIMAL(12, 2) NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
);
The customer's name is stored in one authoritative customers record. Orders refer to the customer using customer_id rather than repeating the customer's name in every order row.
3.2 Example of a Denormalized Reporting Table
CREATE TABLE order_reporting (
order_id INT PRIMARY KEY,
customer_id INT NOT NULL,
customer_name VARCHAR(100) NOT NULL,
order_total DECIMAL(12, 2) NOT NULL,
order_date DATE NOT NULL
);
This table can support reporting queries without joining the customers table to retrieve the stored customer name. It is an example of a reporting structure, not necessarily a replacement for the normalized transactional tables.
4. Common Denormalization Techniques
4.1 Duplicating Frequently Used Attributes
A frequently accessed attribute may be copied into another table when repeated joins are costly and the duplication has a clear business meaning.
Example: storing a product name in an order-item snapshot so that an order preserves the product description used when the order was placed.
CREATE TABLE order_items (
order_item_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
product_name_snapshot VARCHAR(150) NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL
);
A snapshot can be appropriate because product names and prices may change over time. The copied values represent historical order details rather than simply duplicating current product information.
4.2 Storing Precomputed Aggregate Values
Aggregates such as total sales, total order count, and inventory totals may be stored when recalculating them from a large number of rows is expensive.
For example, a customer summary table could contain a precomputed order count:
CREATE TABLE customer_summary (
customer_id INT PRIMARY KEY,
total_orders INT NOT NULL DEFAULT 0,
total_spent DECIMAL(14, 2) NOT NULL DEFAULT 0.00
);
The application or a controlled database process must update these values whenever relevant orders are created, changed, cancelled, or refunded. The summary must have a clearly defined refresh and reconciliation strategy.
4.3 Summary Tables
Summary tables store aggregated information rather than every detailed transaction. They are useful for dashboards and reports that repeatedly display the same totals by day, month, category, or region.
CREATE TABLE daily_sales_summary (
sales_date DATE PRIMARY KEY,
total_orders INT NOT NULL,
total_sales DECIMAL(14, 2) NOT NULL
);
A scheduled process can refresh this table using source transaction data.
SELECT
DATE(order_date) AS sales_date,
COUNT(*) AS total_orders,
SUM(order_total) AS total_sales
FROM orders
GROUP BY DATE(order_date);
The query above calculates daily totals. A separate controlled process would insert or update the resulting values in daily_sales_summary.
4.4 Combining Tables for Read-Heavy Workloads
In certain applications, commonly joined information may be combined into a read-optimized table or view. A regular MySQL view provides a saved query definition but generally does not store the query results physically.
For example:
CREATE VIEW order_details_view AS
SELECT
o.order_id,
o.customer_id,
c.customer_name,
o.order_total
FROM orders AS o
JOIN customers AS c
ON o.customer_id = c.customer_id;
This view simplifies query reuse, but it is not automatically a denormalized materialized table. MySQL does not provide native materialized views in the same way some other database systems do. To persist calculated results, developers typically use a summary table and an explicit refresh or update process.
4.5 Maintaining Counters and Totals
Instead of counting all related rows for every request, an application may maintain a counter. For example, a product record may contain a cached review count.
SELECT COUNT(*) AS review_count
FROM product_reviews
WHERE product_id = 10;
If this query becomes expensive at scale, a stored counter may be considered. However, the counter must be updated reliably, including when reviews are added, deleted, moderated, or reassigned.
5. When Should Denormalization Be Used?
Denormalization should be based on measured requirements rather than assumptions that fewer joins always mean better performance.
5.1 Appropriate Scenarios
- A frequently executed reporting query joins several large tables and profiling identifies those joins as a significant bottleneck.
- A dashboard repeatedly requests the same aggregates over a large volume of transactions.
- An application needs historical snapshots of names, descriptions, prices, or addresses as they were at a specific point in time.
- A read-heavy workload benefits from a dedicated reporting or analytics structure.
- Repeated calculations are expensive and can be safely maintained as derived values.
5.2 When Denormalization Should Be Avoided
- The application has no measured performance problem.
- The database is small and its current queries already perform well.
- Duplicated values change frequently and are difficult to synchronize.
- There is no reliable method for rebuilding or reconciling stored summaries.
- The extra storage, write overhead, and maintenance complexity outweigh the read benefit.
- The team cannot clearly define which copy of a duplicated value is authoritative.
5.3 Decision-Making Checklist
- Identify the slow or expensive query.
- Measure its execution time and workload frequency.
- Inspect the query plan using EXPLAIN or EXPLAIN ANALYZE where supported by the MySQL version and query type.
- Check indexes, filtering conditions, joins, and table statistics.
- Try query and index improvements first.
- Estimate the read-performance benefit of denormalization.
- Define synchronization, failure recovery, and reconciliation procedures.
- Test the proposed design under realistic read and write workloads.
- Document the reason for the denormalization and review it periodically.
6. Risks and Challenges of Denormalization
6.1 Data Inconsistency
If the same fact is stored in several places, one copy may be updated while another remains unchanged. This creates conflicting values.
For example, a customer may have a new name in the customers table while a reporting table still contains the old name.
Prevention: Define an authoritative source, use reliable update processes, and regularly reconcile duplicated data.
6.2 Increased Storage Requirements
Duplicated columns, summary rows, and reporting tables consume additional disk space. They can also increase backup size and affect cache efficiency.
Prevention: Store only useful duplicated attributes, monitor storage growth, and remove redundant structures that no longer provide a measurable benefit.
6.3 More Complex Write Operations
An insert, update, or delete may need to update the original table and one or more derived structures. This makes write logic more complicated.
Prevention: Use transactions for related changes within the same transactional database where appropriate, and implement robust retry and reconciliation mechanisms for asynchronous updates.
6.4 Stale Summary Data
A scheduled summary refresh may run periodically rather than immediately. Reports can therefore show slightly outdated results.
Prevention: Define an acceptable freshness target, display the last refresh time when relevant, and use incremental updates or more frequent refreshes if required.
6.5 Difficult Maintenance
Future developers may not know why data has been duplicated or which process maintains it.
Prevention: Document the business reason, authoritative data source, refresh method, dependencies, and recovery procedure.
7. Maintaining Data Consistency in Denormalized Designs
7.1 Choose a Source of Truth
For each duplicated value, identify the authoritative source. Derived copies should not become competing sources of truth unless the business explicitly defines that behavior.
Example:
- Source table: orders.
- Derived structure: daily_sales_summary.
- Refresh method: scheduled aggregation or reliable incremental processing.
- Validation method: compare summary values with aggregates calculated from orders.
7.2 Use Transactions for Related Updates
When the original data and its derived values must change together, a transaction can help keep the changes atomic if all affected operations participate in the same transaction and use transactional storage engines such as InnoDB.
START TRANSACTION;
INSERT INTO orders (
customer_id,
order_total,
order_date
)
VALUES (1, 2500.00, '2026-10-17');
UPDATE customer_summary
SET
total_orders = total_orders + 1,
total_spent = total_spent + 2500.00
WHERE customer_id = 1;
COMMIT;
Important: This example assumes the customer_summary row already exists and that the order and summary updates are part of the same reliable business operation. Production implementations must handle errors, rollbacks, retries, refunds, cancellations, and cases where the summary row does not exist. The example should not be used as a universal order-processing implementation without these safeguards.
7.3 Use Scheduled Reconciliation
Even a carefully designed update process can encounter failures, bugs, or operational mistakes. Reconciliation compares derived data with the original source and identifies discrepancies.
SELECT
customer_id,
COUNT(*) AS calculated_orders,
SUM(order_total) AS calculated_spending
FROM orders
GROUP BY customer_id;
The result can be compared with customer_summary. Differences should be investigated and corrected through a controlled process.
7.4 Plan for Asynchronous Processing
For large systems, summary updates may be handled by background jobs or event-driven processing. This can reduce the work performed during a user request but may introduce a delay before derived data becomes current.
When using asynchronous processing, define retry behavior, duplicate-event handling, monitoring, error queues where appropriate, and a method for rebuilding derived data.
8. Introduction to Database Design Review
A database design review is a structured assessment of whether the database schema meets the application's functional, performance, security, and maintenance requirements.
It is useful before a new application launches, before a major schema migration, when query performance degrades, and during periodic technical reviews.
8.1 Main Objectives of a Design Review
- Verify that tables represent the correct business entities.
- Check that columns have appropriate data types and constraints.
- Confirm that primary and foreign keys reflect the intended relationships.
- Identify unnecessary duplication and normalization problems.
- Review indexes and query performance.
- Check transaction behavior and data integrity.
- Evaluate security, access permissions, backup, and recovery practices.
- Assess whether the schema can support expected data growth.
- Document design decisions and future improvements.
8.2 Database Design Review Stages
- Requirement review: Understand business entities, workflows, reports, and data retention needs.
- Schema review: Inspect tables, columns, primary keys, foreign keys, and constraints.
- Relationship review: Verify one-to-one, one-to-many, and many-to-many relationships.
- Normalization review: Identify redundant data and update anomalies.
- Performance review: Evaluate high-frequency queries, indexes, joins, and execution plans.
- Security review: Check user privileges and sensitive-data handling.
- Reliability review: Examine transactions, backup, recovery, and maintenance processes.
- Documentation: Record issues, severity, recommendations, owners, and follow-up actions.
9. Reviewing Tables and Columns
9.1 Use Clear Table Names
Table names should communicate the type of information stored in the table. Avoid names that are unclear, inconsistent, or overly abbreviated.
Examples of clear names include customers, orders, products, and order_items.
9.2 Use Appropriate Data Types
Choose data types based on the actual meaning and expected range of values.
| Data Type |
Typical Use |
Review Consideration |
| INT |
Numeric identifiers and counts |
Ensure the range is sufficient. |
| BIGINT |
Large identifiers or counts |
Use when a larger range is required. |
| VARCHAR(n) |
Names, emails, and variable-length text |
Choose a reasonable maximum length. |
| TEXT |
Long text content |
Consider retrieval and indexing requirements. |
| DECIMAL(p,s) |
Currency and exact decimal values |
Choose suitable precision and scale. |
| DATE |
Calendar dates |
Use when time-of-day is unnecessary. |
| DATETIME |
Date and time values |
Define timezone handling conventions. |
| BOOLEAN |
True/false flags |
MySQL implements BOOLEAN as a synonym for TINYINT(1). |
9.3 Avoid Unnecessary Columns
Remove unused fields when it is safe to do so. Unnecessary columns can confuse developers, increase storage requirements, and complicate application logic.
Before removing a column, inspect application dependencies, reports, stored routines, scheduled jobs, and integrations. Use a migration plan rather than deleting production data without validation.
9.4 Define Nullability Carefully
Use NOT NULL when a value is mandatory. Allow NULL when the absence of a value has a meaningful business interpretation.
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(150) NOT NULL,
description TEXT NULL,
price DECIMAL(10, 2) NOT NULL
);
In this example, a product must have a name and price, while its description may be absent.
10. Reviewing Primary Keys, Foreign Keys, and Constraints
10.1 Primary Key Review
A primary key uniquely identifies each row. It should be stable, unique, and appropriate for the table.
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL
);
Check that every table has an appropriate primary key and that the key is not unnecessarily mutable.
10.2 Foreign Key Review
Foreign keys help maintain referential integrity between related tables.
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(150) NOT NULL,
category_id INT,
CONSTRAINT fk_products_category
FOREIGN KEY (category_id)
REFERENCES categories(category_id)
);
Review whether child records should be prevented from referencing nonexistent parent records and whether deletion or update behavior is appropriate for the business.
10.3 Unique Constraints
Unique constraints prevent duplicate values in columns or combinations of columns that must be unique.
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(80) NOT NULL UNIQUE
);
Review whether the uniqueness rule matches the business requirements. For example, some systems allow multiple accounts to share a contact email, while others require a unique email address.
10.4 Check Constraints
Check constraints can enforce valid values for a column or a row.
CREATE TABLE inventory (
product_id INT PRIMARY KEY,
quantity INT NOT NULL,
CONSTRAINT chk_inventory_quantity
CHECK (quantity >= 0)
);
Ensure that the MySQL version in use enforces the constraints you depend on. Also validate business rules in application logic where appropriate.
11. Reviewing Database Relationships
11.1 One-to-One Relationship
A one-to-one relationship exists when one record in a table is associated with at most one record in another table, according to the business rule.
Example: a user and a separate user_profile record. A unique foreign key can enforce the one-to-one relationship.
CREATE TABLE user_profiles (
profile_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL UNIQUE,
biography TEXT,
CONSTRAINT fk_profile_user
FOREIGN KEY (user_id)
REFERENCES users(user_id)
);
11.2 One-to-Many Relationship
A one-to-many relationship allows one parent record to be associated with multiple child records.
Example: one customer can place many orders. The customer identifier belongs in the orders table.
11.3 Many-to-Many Relationship
A many-to-many relationship is normally represented by a junction table.
Example: students can enroll in many courses, and each course can have many students. A student_courses table can connect the two entities.
CREATE TABLE student_courses (
student_id INT NOT NULL,
course_id INT NOT NULL,
enrollment_date DATE NOT NULL,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
Review whether the junction table has the correct composite or surrogate key and whether duplicate relationships are allowed by the business requirements.
12. Reviewing Normalization and Data Redundancy
12.1 First Normal Form (1NF)
First Normal Form requires columns to represent individual values rather than storing multiple independent values in a single field as an ordinary list. Repeating groups should be represented in an appropriate relational structure.
For example, storing multiple phone numbers in one comma-separated column makes searching and validation difficult. A separate customer_phone table may be more appropriate when each phone number needs independent management.
12.2 Second Normal Form (2NF)
Second Normal Form requires a table to satisfy 1NF and ensures that every non-key attribute depends on the whole candidate key, rather than only part of a composite candidate key.
This is particularly important in tables with composite keys.
12.3 Third Normal Form (3NF)
Third Normal Form requires a table to satisfy 2NF and avoid inappropriate transitive dependencies of non-key attributes on candidate keys.
For example, if an order table stores both customer_id and customer_city when customer_city depends on customer_id, changes to the customer's city can require updates to multiple order rows. Keeping the current city in the customers table can avoid that redundancy when current customer information is the intended meaning.
12.4 Distinguishing Useful Snapshots from Accidental Duplication
Not every repeated value is a design mistake. An order's unit_price_snapshot may be necessary to preserve the price agreed upon at purchase time. The current price in the products table represents a different business fact.
During a design review, determine whether repeated data represents the same fact copied unnecessarily or a separate historical fact with its own meaning.
13. Reviewing Indexes for Performance
13.1 What Is an Index?
An index is a data structure that can help MySQL find matching rows without scanning every row in a table. Indexes can improve query performance, but they require storage and add work to inserts, updates, and deletes.
13.2 Primary and Unique Indexes
Primary keys and unique constraints generally create indexes automatically. Verify that these indexes match the data integrity requirements and that additional indexes do not duplicate their purpose unnecessarily.
13.3 Index Columns Used in Filters and Joins
Columns frequently used in WHERE conditions, join predicates, sorting, or grouping may be candidates for indexing. The best index depends on the query pattern, table size, data distribution, and selectivity.
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date);
This composite index may help queries that filter by customer_id and, in suitable cases, also filter or order by order_date. Column order matters because MySQL can most directly use the leftmost prefix of a composite index.
13.4 Avoid Excessive Indexing
Creating an index for every column is not a good practice. Unnecessary indexes consume storage and slow down write operations.
Review index usage and remove redundant indexes only after checking query patterns, constraints, workload evidence, and production dependencies.
13.5 Use EXPLAIN to Review Queries
The EXPLAIN statement provides information about how MySQL plans to execute a query.
EXPLAIN
SELECT
order_id,
order_date,
order_total
FROM orders
WHERE customer_id = 1
ORDER BY order_date DESC;
Review the selected access method, possible and chosen indexes, estimated rows, filtering behavior, and other plan details. The meaning of particular fields depends on the MySQL version and query.
Where supported and appropriate, EXPLAIN ANALYZE can execute a query and report actual execution details. Use caution when analyzing expensive queries on production systems because the statement executes the query.
14. Reviewing Query Performance
14.1 Identify Slow Queries
Start with evidence. Use the slow query log, Performance Schema, application monitoring, and query profiling tools to identify queries that consume substantial time or resources.
14.2 Avoid SELECT *
Select only the columns required by the application. This can reduce data transfer and avoid retrieving large or unnecessary fields.
-- Less specific
SELECT *
FROM orders
WHERE customer_id = 1;
-- More specific
SELECT order_id, order_date, order_total
FROM orders
WHERE customer_id = 1;
14.3 Review Join Conditions
Ensure that joins use the intended keys and that the columns have compatible data types and appropriate indexes. Accidental many-to-many joins can multiply rows and produce incorrect totals.
14.4 Avoid Functions on Filtered Columns When Possible
Applying a function to an indexed column can make a conventional index less useful for some predicates. A range condition may be more index-friendly.
-- May make ordinary index use harder
SELECT *
FROM orders
WHERE YEAR(order_date) = 2026;
-- Range-based alternative
SELECT *
FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01';
The range-based query can use a suitable index on order_date more effectively in many cases. Confirm the actual plan rather than assuming a particular optimization.
14.5 Review Pagination
Large OFFSET values can require MySQL to scan and discard many rows. For deep pagination, consider keyset pagination when the application can use a stable ordering key.
SELECT order_id, order_date, order_total
FROM orders
WHERE order_id > 10000
ORDER BY order_id
LIMIT 50;
This example uses order_id as a cursor. A production query must use a cursor and ordering that match the intended sort order and handle ties where applicable.
15. Database Security and Access Review
15.1 Follow the Principle of Least Privilege
Database accounts should receive only the permissions required for their tasks. Application accounts generally should not have unrestricted administrative privileges.
CREATE USER 'app_reader'@'localhost'
IDENTIFIED BY 'Use-A-Strong-Unique-Secret';
GRANT SELECT ON shop_db.*
TO 'app_reader'@'localhost';
This is an illustrative example. Use a securely managed secret, the correct host restrictions, and your organization's credential-management practices. Avoid storing passwords in source code or sharing credentials between unrelated services.
15.2 Protect Sensitive Data
- Identify personal, financial, and confidential fields.
- Limit access to sensitive columns and tables.
- Use encrypted connections and suitable encryption controls where required.
- Avoid exposing sensitive values in logs and error messages.
- Use masking or anonymization for development and test data where appropriate.
- Establish retention and deletion policies for data that is no longer needed.
15.3 Use Parameterized Queries
Applications should use prepared statements or parameterized queries rather than constructing SQL by concatenating untrusted input.
SELECT order_id, order_total
FROM orders
WHERE customer_id = ?;
The application binds the customer ID as a parameter through its database driver. The question mark represents a parameter marker used by prepared-statement interfaces, not a literal value to execute as a standalone MySQL query.
16. Transactions, Reliability, and Data Integrity
16.1 Use Transactions for Related Changes
Transactions group related database operations so that the application can commit them together or roll them back when a failure occurs.
START TRANSACTION;
UPDATE accounts
SET balance = balance - 500
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 500
WHERE account_id = 2;
COMMIT;
This example illustrates grouped updates, but a real transfer must also validate available funds, verify that both accounts exist, check affected rows, handle errors, and roll back on failure. Appropriate constraints and concurrency controls are important.
16.2 Understand ACID Properties
- Atomicity: A transaction's operations are committed as a unit or rolled back.
- Consistency: Transactions should preserve defined integrity rules.
- Isolation: Concurrent transactions are controlled according to the configured isolation level.
- Durability: Committed changes are intended to survive failures according to the database's durability configuration.
16.3 Review Concurrency and Locking
Inspect long-running transactions, deadlocks, lock waits, and concurrent updates to shared summary values. A denormalized counter can become a contention point when many transactions update the same row.
Keep transactions appropriately short, use consistent update patterns, and implement safe retry handling for errors that can be retried.
17. Backup, Recovery, and Maintenance Review
17.1 Establish a Backup Strategy
Backups should be planned according to the importance of the data, acceptable data loss, recovery time objectives, and available infrastructure.
- Define backup frequency and retention periods.
- Use an appropriate logical or physical backup approach.
- Store backups securely and restrict access.
- Consider binary logs and point-in-time recovery where appropriate.
- Monitor backup jobs and investigate failures.
17.2 Test Restoration
A backup is useful only if it can be restored successfully. Periodically restore backups in a safe test environment and verify the recovered data and application behavior.
17.3 Review Schema Migrations
Schema changes should be version-controlled and tested before production deployment. Review whether migrations lock large tables, affect application compatibility, or require data backfills.
For large tables, use a migration strategy that accounts for the specific MySQL version, storage engine, table size, and operational constraints.
18. Scalability and Future Growth
18.1 Estimate Data Growth
Review expected transaction volume, data retention, index growth, and reporting needs. A design that performs well with a few thousand rows may behave differently with millions of rows.
18.2 Avoid Premature Optimization
Do not add denormalized tables, caches, or complex indexing before there is evidence that they are needed. Start with a sound schema and optimize the most important measured bottlenecks.
18.3 Separate Transactional and Analytical Needs When Appropriate
Transactional workloads often require reliable updates to individual records, while analytical workloads may require large aggregations across many records. A dedicated reporting schema or analytics pipeline can help isolate these needs when the workload justifies the added complexity.
18.4 Plan for Archiving and Retention
Large historical tables can increase storage and maintenance costs. Define which data must remain available online, which records can be archived, and what retention rules apply. Archiving should preserve auditability and satisfy applicable legal or business requirements.
19. Complete Database Design Review Checklist
Use this checklist when reviewing a MySQL database design.
19.1 Schema and Data Structure
- Table names are clear and consistent.
- Each table represents a defined business entity or relationship.
- Column names are meaningful and consistent.
- Data types and column lengths are appropriate.
- NULL and NOT NULL choices match business rules.
- Primary keys are defined appropriately.
- Foreign keys and unique constraints enforce required relationships.
19.2 Normalization and Denormalization
- Repeated facts have been reviewed for unnecessary duplication.
- Normalization issues and update anomalies have been identified.
- Denormalization is justified by a measurable requirement.
- Every duplicated value has a defined source of truth.
- Summary refresh and reconciliation procedures are documented.
- Historical snapshots are distinguished from accidental duplication.
19.3 Performance
- High-frequency and slow queries have been identified.
- Query plans have been reviewed using appropriate tools.
- Indexes support important filters, joins, and ordering patterns.
- Redundant or unused indexes have been assessed carefully.
- Large scans, expensive sorts, and unnecessary data retrieval have been reviewed.
- Performance has been tested with realistic data volumes.
19.4 Security and Reliability
- Database users follow least-privilege principles.
- Credentials and sensitive data are protected.
- Parameterized queries are used for untrusted input.
- Transactions and concurrency behavior have been reviewed.
- Backup and restoration procedures have been tested.
- Schema migrations are version-controlled and tested.
- Monitoring and alerting are in place for important failures.
20. Practical Project: Review and Optimize an E-Commerce Database
20.1 Project Scenario
An online store contains customers, products, orders, and order_items tables. The business reports that its monthly sales dashboard is slow. Developers are considering copying all customer and product details into the orders table.
The objective is to review the design, identify the actual bottleneck, and select the simplest reliable optimization.
20.2 Create the Initial Tables
CREATE DATABASE ecommerce_review;
USE ecommerce_review;
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
customer_name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(150) NOT NULL,
current_price DECIMAL(10, 2) NOT NULL
);
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATETIME NOT NULL,
status VARCHAR(30) NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
);
CREATE TABLE order_items (
order_item_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_items_order
FOREIGN KEY (order_id)
REFERENCES orders(order_id),
CONSTRAINT fk_items_product
FOREIGN KEY (product_id)
REFERENCES products(product_id)
);
20.3 Review the Reporting Query
A monthly sales report might calculate totals from order items:
SELECT
YEAR(o.order_date) AS sales_year,
MONTH(o.order_date) AS sales_month,
SUM(oi.quantity * oi.unit_price) AS total_sales
FROM orders AS o
JOIN order_items AS oi
ON oi.order_id = o.order_id
WHERE o.status = 'completed'
AND o.order_date >= '2026-01-01'
AND o.order_date < '2027-01-01'
GROUP BY
YEAR(o.order_date),
MONTH(o.order_date)
ORDER BY
sales_year,
sales_month;
Before denormalizing, inspect the query plan, table sizes, existing indexes, the frequency of the report, and the distribution of order statuses.
20.4 Evaluate Suitable Indexes
Depending on the workload and execution plan, the following indexes may be candidates for testing:
CREATE INDEX idx_orders_status_date
ON orders (status, order_date);
CREATE INDEX idx_order_items_order
ON order_items (order_id);
The foreign key and primary key definitions may already create supporting indexes depending on the table design and MySQL behavior. Inspect existing indexes before adding new ones, and verify whether each proposed index improves the actual query.
20.5 Consider a Monthly Summary Table
If the dashboard is read frequently and the underlying tables are large, a summary table may be more suitable than duplicating every customer and product field into transactional tables.
CREATE TABLE monthly_sales_summary (
sales_year SMALLINT NOT NULL,
sales_month TINYINT NOT NULL,
total_sales DECIMAL(16, 2) NOT NULL,
completed_order_count INT NOT NULL,
refreshed_at DATETIME NOT NULL,
PRIMARY KEY (sales_year, sales_month),
CHECK (sales_month BETWEEN 1 AND 12)
);
The application or a scheduled process can populate and refresh the summary. The refresh process should calculate the same business metrics as the source query and account for changed order statuses, refunds, cancellations, and late-arriving data.
20.6 Validate the Results
- Run the original query and record its execution time.
- Inspect the query plan and test appropriate indexes.
- Implement a summary refresh process if the workload justifies it.
- Compare the summary totals against a fresh calculation from the source tables.
- Test changes to completed orders, refunds, and cancellations.
- Measure dashboard latency and write overhead after the change.
- Document the design decision and the recovery procedure.
20.7 Expected Learning Outcomes
- Recognize that slow queries should be measured before redesigning tables.
- Understand that indexes may solve some performance issues without duplicating data.
- Distinguish transactional tables from reporting summaries.
- Maintain derived values reliably and validate them against the source.
- Balance query speed, consistency, storage, and maintenance effort.
21. Common Database Design Mistakes
| Mistake |
Why It Is a Problem |
Recommended Practice |
| Denormalizing without measurement |
Creates complexity without a proven benefit |
Profile first and compare alternatives. |
| Duplicating values without ownership rules |
Copies can become inconsistent |
Define an authoritative source and update process. |
| Creating too many indexes |
Increases storage and write overhead |
Index based on real query patterns. |
| Missing foreign keys |
Can allow invalid relationships |
Use appropriate referential constraints. |
| Using unsuitable data types |
Can cause incorrect values or waste resources |
Choose types based on the domain and range. |
| Ignoring slow queries |
Application performance may degrade as data grows |
Monitor and review execution plans. |
| Using SELECT * everywhere |
Retrieves unnecessary data |
Select only required columns. |
| Not testing backup restoration |
Recovery may fail when needed |
Perform regular restore tests. |
| Keeping undocumented summary tables |
Maintenance and troubleshooting become difficult |
Document refresh, ownership, and reconciliation. |
| Making risky production changes directly |
May cause downtime or data loss |
Use tested, version-controlled migrations. |
22. Best Practices for MySQL Database Design
22.1 Design Around Business Requirements
Understand the entities, relationships, transactions, reports, and retention requirements before creating tables. A technically valid schema can still be unsuitable if it does not represent the business correctly.
22.2 Normalize the Core Transactional Model
Use normalization to reduce accidental duplication and make updates predictable. Introduce denormalization only where a specific performance or historical-data requirement justifies it.
22.3 Use Constraints to Protect Data
Use primary keys, foreign keys, unique constraints, NOT NULL, and suitable check constraints to enforce important rules in the database. Application validation remains useful, but it should not be the only protection for critical integrity requirements.
22.4 Select Appropriate Data Types
Use DECIMAL for exact monetary values, appropriate date and time types for temporal data, and numeric identifiers with sufficient capacity. Avoid storing dates, amounts, or numbers as arbitrary text unless there is a clear reason.
22.5 Index Based on Real Workloads
Use execution plans and monitoring to identify useful indexes. Review the complete query patterns, including joins, filters, sorting, and grouping, rather than indexing columns in isolation.
22.6 Use Consistent Naming Conventions
Adopt consistent naming for tables, columns, indexes, and constraints. Clear names improve readability and reduce mistakes during development and maintenance.
22.7 Document Important Design Decisions
Record why a table exists, what each duplicated field means, which process updates derived data, and how inconsistencies can be repaired. Documentation is especially important for denormalized structures.
22.8 Test with Realistic Data Volumes
Small test datasets may not reveal expensive joins, large sorts, locking issues, or inefficient scans. Use representative data volumes and concurrency patterns before making major design decisions.
22.9 Monitor and Review Periodically
Database workloads evolve. Review slow queries, index usage, storage growth, summary freshness, backup results, and schema complexity regularly.
22.10 Make Changes Safely
Back up important data, test schema changes in a staging environment, prepare rollback or recovery plans, and verify application compatibility before deploying database changes.
23. Interview Questions and Answers
Q1. What is denormalization in MySQL?
Answer: Denormalization is the deliberate introduction of redundant or precomputed data to improve selected read operations, simplify reports, or reduce repeated calculations.
Q2. What is the main difference between normalization and denormalization?
Answer: Normalization reduces unnecessary redundancy and update anomalies, while denormalization introduces controlled redundancy to optimize particular workloads.
Q3. Does denormalization always improve performance?
Answer: No. It may improve certain reads but can increase storage, write overhead, contention, and maintenance complexity. Performance should be measured before and after the change.
Q4. What is a summary table?
Answer: A summary table stores precomputed aggregates, such as daily sales or monthly order counts, to reduce repeated calculations over large datasets.
Q5. What is the main risk of duplicated data?
Answer: The copies can become inconsistent when one value changes and the others are not updated. A reliable source of truth and synchronization process are essential.
Q6. What is a database design review?
Answer: It is a structured evaluation of schema design, relationships, constraints, normalization, indexes, query performance, security, reliability, and maintainability.
Q7. How can you identify a slow MySQL query?
Answer: Use monitoring, the slow query log, Performance Schema, and query execution plans. EXPLAIN helps inspect the optimizer's plan, while EXPLAIN ANALYZE can provide actual execution details when supported.
Q8. Why are foreign keys important?
Answer: Foreign keys help prevent child records from referring to nonexistent parent records and enforce defined relationships between tables.
Q9. Should every column have an index?
Answer: No. Indexes should be based on query patterns and constraints because unnecessary indexes consume storage and increase write costs.
Q10. What is the difference between a MySQL view and a summary table?
Answer: A normal view stores a query definition and retrieves data through that query. A summary table physically stores derived rows and requires an explicit process to keep them current.
Q11. How can consistency be maintained in a denormalized database?
Answer: Define a source of truth, update related values reliably, use transactions where appropriate, handle retries and failures, and reconcile derived data against the source.
Q12. What should be reviewed before adding a denormalized table?
Answer: Review the workload, query plan, existing indexes, expected performance benefit, data freshness requirements, synchronization costs, storage, and recovery strategy.
Q13. Why are transactions important?
Answer: Transactions help ensure that related database changes are committed together or rolled back together, supporting reliable updates when used correctly.
Q14. Why should backups be tested?
Answer: Successful backup creation does not guarantee successful recovery. Restore tests verify that the data can actually be recovered and used.
Q15. When is a denormalized reporting structure useful?
Answer: It can be useful when frequent reporting queries repeatedly calculate expensive aggregates or join large tables and measured performance requirements justify maintaining a separate read-optimized structure.
24. Practice Exercises
Exercise 1: Identify Redundancy
Consider an orders table that stores customer_id, customer_name, customer_city, order_date, and order_total.
- Identify which fields describe the customer and which describe the order.
- Explain the possible update anomaly if the customer changes city.
- Suggest a normalized design.
- Describe a situation where a customer name snapshot might be justified.
Exercise 2: Evaluate Denormalization
A dashboard calculates monthly sales from several million order-item records every time a user opens the page.
- List the measurements you would collect before changing the schema.
- Identify indexes and query improvements worth testing.
- Propose a monthly summary table if justified.
- Explain how you would keep the summary current.
Exercise 3: Review Indexes
Write a query that finds all completed orders for a particular customer within a date range. Use EXPLAIN to inspect its plan and recommend a suitable index based on the results.
Exercise 4: Data Consistency
A summary table stores total_spent for every customer. Explain how you would handle new orders, order updates, refunds, cancellations, failed background jobs, and reconciliation.
Exercise 5: Design Review Report
Prepare a review report for a sample database. Include schema quality, relationships, constraints, indexes, slow queries, security, backups, denormalization opportunities, risk levels, and recommended next steps.
25. Quick Revision Notes
- Denormalization deliberately introduces controlled redundancy or precomputed values.
- Its main purpose is to improve specific read-heavy or analytical workloads.
- Normalization reduces unnecessary duplication and helps prevent update anomalies.
- Denormalization can increase storage, write complexity, and consistency risks.
- Measure performance before changing the database design.
- Check indexes, joins, filtering conditions, and execution plans before denormalizing.
- Use summary tables for justified reporting and aggregation workloads.
- MySQL views are not automatically materialized tables.
- Every duplicated or derived value should have a clear source of truth.
- Use transactions where related changes need to be committed atomically.
- Use constraints and suitable data types to protect data integrity.
- Review keys, relationships, indexes, security, and migrations systematically.
- Test backup restoration and validate recovery procedures.
- Document design decisions and monitor performance after deployment.
26. Conclusion
Denormalization, database design review, and best practices are closely connected parts of building efficient MySQL applications. Normalization provides a strong foundation for reliable transactional data, while denormalization can improve selected reporting and read-heavy workloads when it is supported by measurable evidence.
A successful database design review examines more than query speed. It evaluates data types, keys, relationships, constraints, indexes, transaction behavior, security, backup and recovery, scalability, and maintainability.
The best approach is to begin with a clear, well-normalized design, monitor actual workload behavior, optimize queries and indexes, and introduce denormalization only when the benefits justify the extra complexity. Reliable synchronization, reconciliation, documentation, and testing help keep the database accurate as it grows.
Continue Learning: Explore the MySQL Training Course to strengthen your database design and SQL skills, or register for a course demo to learn more about the training.