如何設計不會在日後造成問題的資料庫結構
How to Design a Database Schema That Won't Become a Problem Later
良好的資料庫結構應清楚表達資料關係和業務規則,方便日後合理修改,而不是預測所有未來需求。文章以 PostgreSQL 網店資料為例,説明分開客戶、產品、訂單及訂單項目資料表,並以約束保護資料完整性、保留下單時的價格。索引應按實際查詢模式評估,並用 EXPLAIN 檢查查詢計劃。
A database schema can look perfectly fine when an application has only a few users and a handful of records. The problems often appear months later, when new features are added, relationships become more complicated, and developers start struggling to change existing tables without breaking something else.
Good database design is not about predicting every future requirement. It is about making today's data structure clear enough to support reasonable changes tomorrow.
Start With Relationships, Not Tables
Imagine you're building an e-commerce application. You need to store customers, products, orders, and order items.
A beginner might create one large table containing the customer's name, product name, price, order date, and quantity in every row.
This appears convenient initially, but it creates duplication. A customer placing ten orders may have their information repeated across multiple rows. Updating that information becomes unnecessarily complicated.
A more maintainable approach separates entities according to their responsibilities.
Customers belong in a customers table. Products belong in a products table. Each order belongs in an orders table, while individual products purchased in an order belong in an order_items table.
The relationships between these tables allow the application to represent real business rules without repeating the same information everywhere.
A Practical SQL Example
Consider this simplified PostgreSQL schema:
CREATE TABLE customers (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(150) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price NUMERIC(10, 2) NOT NULL
CHECK (price >= 0)
);
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id BIGINT NOT NULL
REFERENCES customers(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE order_items (
order_id BIGINT NOT NULL REFERENCES orders(id),
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(10, 2) NOT NULL
CHECK (unit_price >= 0),
PRIMARY KEY (order_id, product_id)
);
This example establishes several important rules. Customer emails must be unique, products cannot have negative prices, and an order item cannot reference a nonexistent product or order.
The unit_price column also deserves attention. It records the price associated with the item when the order is created. If the product's current price changes later, the historical order can still retain its original price.
For more details about these database features, see the official PostgreSQL documentation on constraints.
Don't Rely on the Frontend to Protect Your Data
Frontend validation is useful for giving users immediate feedback, but it should not be the only layer enforcing important business rules.
For example, a form might prevent a customer from submitting a quantity of zero. However, someone could bypass that form and send a request directly to the backend.
The database constraint provides another layer of protection by rejecting invalid data even when an application request contains a mistake.
Not every rule belongs in a database constraint. Complex business workflows may need validation in the application service as well. The goal is to enforce essential data integrity rules at the appropriate layers rather than assuming every request will be valid.
Think About Queries Before Adding Indexes
As the application grows, developers often add indexes whenever a query becomes slow. Indexes can help, but adding them without understanding the workload can increase storage requirements and slow down writes.
Start by identifying the queries the application actually runs.
If customers frequently retrieve their orders using customer_id, an index on that column may help. PostgreSQL automatically indexes primary keys and unique constraints, but it does not automatically create an index on every referencing foreign-key column.
You might add an index like this:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
Whether this index is useful depends on the query patterns, table size, and existing indexes. Use EXPLAIN to inspect how PostgreSQL plans to execute a query, and test with representative data before making performance decisions.
The official PostgreSQL documentation on indexes explains the available index types and their trade-offs.
Avoid Designing for Imaginary Requirements
Future flexibility is valuable, but excessive abstraction can make a database harder to understand.
Creating dozens of tables for features that may never exist adds complexity without necessarily improving maintainability. Storing every possible attribute in a generic key-value structure can also make ordinary queries more difficult.
Start with known business requirements. Separate data where relationships and responsibilities justify it, enforce important constraints, and review the design when actual requirements change.
A schema should evolve with the application, not attempt to predict every possible future feature.
Review the Schema Before the Application Grows
A useful database review asks a few practical questions.
Can the same information be updated consistently? Are relationships enforced? Can the application represent historical transactions correctly? Do important queries have suitable indexes? Can a developer understand the purpose of each table without guessing?
These questions are relevant to web applications where reliable content management, publishing workflows, and structured website data matter.
For developers exploring Web Development SEO, RankifyDev offers website development and SEO-related services. Technical architecture, maintainable code, and a well-organized website all contribute to building a reliable online presence.
A well-designed database will not automatically make an application scalable, but it gives developers a clearer foundation to build on.
The best time to think about database structure is before duplicated data and fragile relationships become difficult to remove. A small amount of planning early on can make future features much easier to implement and maintain.
AI disclosure: This article was prepared with AI assistance. Review the SQL examples and test them in your intended PostgreSQL environment before using them in production.
來源:DEV Community · dev.to