#SQL详解之DDL - 数据库定义语言完整指南
#引言
数据定义语言(Data Definition Language,DDL)是SQL语言的一个重要子集,用于定义和管理数据库结构。DDL命令包括CREATE、ALTER、DROP等,它们负责创建、修改和删除数据库对象,如数据库、表、索引、视图等。本文将详细介绍DDL的各种命令,并对比MySQL和PostgreSQL在DDL实现上的差异。
#1. DDL基础概念
#1.1 什么是DDL?
DDL(Data Definition Language)是SQL语言中用于定义数据库结构的部分,主要包括:
- CREATE:创建数据库对象
- ALTER:修改数据库对象
- DROP:删除数据库对象
- TRUNCATE:清空表数据
DDL语句会自动提交,执行后无法回滚(除了TRUNCATE在某些数据库中)。
#1.2 DDL与DML的区别
| 语言类型 | 主要命令 | 用途 | 事务特性 |
|---|---|---|---|
| DDL | CREATE, ALTER, DROP | 定义/修改数据库结构 | 自动提交 |
| DML | SELECT, INSERT, UPDATE, DELETE | 操作数据 | 需手动提交 |
#2. 数据库操作
#2.1 创建数据库
#MySQL语法:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS company_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE company_db;#PostgreSQL语法:
-- 创建数据库
CREATE DATABASE company_db
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'en_US.UTF-8'
LC_CTYPE = 'en_US.UTF-8'
TABLESPACE = pg_default
CONNECTION LIMIT = -1;
-- 查看数据库
\l -- 或者
SELECT datname FROM pg_database;
-- 连接数据库(通过客户端)
\c company_db;#2.2 删除数据库
#MySQL:
-- 删除数据库
DROP DATABASE IF EXISTS old_database;#PostgreSQL:
-- 删除数据库
DROP DATABASE IF EXISTS old_database;
-- 注意:在PostgreSQL中,删除数据库前需要终止所有连接
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'old_database' AND pid <> pg_backend_pid();#3. 表操作
#3.1 创建表
#MySQL表创建示例:
-- 创建员工表
CREATE TABLE employees (
employee_id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
hire_date DATE NOT NULL,
salary DECIMAL(10, 2) CHECK (salary > 0),
department_id INT,
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),
FOREIGN KEY (department_id) REFERENCES departments(department_id)
) ENGINE=InnoDB CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 创建带分区的表(MySQL 8.0+)
CREATE TABLE sales (
id INT NOT NULL,
sale_date DATE NOT NULL,
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);#PostgreSQL表创建示例:
-- 创建员工表
CREATE TABLE employees (
employee_id SERIAL PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
hire_date DATE NOT NULL,
salary NUMERIC(10, 2) CHECK (salary > 0),
department_id INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_department
FOREIGN KEY (department_id)
REFERENCES departments(department_id)
ON DELETE SET NULL
);
-- 更新updated_at字段的触发器
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_employees_updated_at
BEFORE UPDATE ON employees
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 创建带分区的表(PostgreSQL 10+)
CREATE TABLE sales (
id SERIAL,
sale_date DATE NOT NULL,
amount NUMERIC(10,2)
) PARTITION BY RANGE (sale_date);
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');#3.2 修改表结构
#MySQL ALTER操作:
-- 添加列
ALTER TABLE employees
ADD COLUMN phone VARCHAR(20),
ADD COLUMN manager_id INT;
-- 修改列
ALTER TABLE employees
MODIFY COLUMN salary DECIMAL(12, 2),
CHANGE COLUMN phone mobile_phone VARCHAR(20);
-- 删除列
ALTER TABLE employees
DROP COLUMN manager_id;
-- 添加约束
ALTER TABLE employees
ADD CONSTRAINT chk_salary CHECK (salary > 0),
ADD INDEX idx_hire_date (hire_date);
-- 删除约束
ALTER TABLE employees
DROP CHECK chk_salary,
DROP INDEX idx_hire_date;
-- 重命名表
ALTER TABLE employees RENAME TO staff;#PostgreSQL ALTER操作:
-- 添加列
ALTER TABLE employees
ADD COLUMN phone VARCHAR(20),
ADD COLUMN manager_id INTEGER;
-- 修改列
ALTER TABLE employees
ALTER COLUMN salary TYPE NUMERIC(12, 2),
ALTER COLUMN phone TYPE VARCHAR(25); -- 重命名需要单独操作
-- 重命名列
ALTER TABLE employees RENAME COLUMN phone TO mobile_phone;
-- 删除列
ALTER TABLE employees DROP COLUMN manager_id;
-- 添加约束
ALTER TABLE employees
ADD CONSTRAINT chk_salary CHECK (salary > 0),
ADD CONSTRAINT uk_employee_email UNIQUE (email);
-- 删除约束
ALTER TABLE employees
DROP CONSTRAINT chk_salary,
DROP CONSTRAINT uk_employee_email;
-- 重命名表
ALTER TABLE employees RENAME TO staff;#3.3 删除表
#MySQL:
-- 删除表
DROP TABLE IF EXISTS employees;
-- 临时删除(MySQL 8.0+回收站功能)
SET SESSION sql_log_bin = 0; -- 仅用于演示
-- 实际上MySQL没有内置回收站功能,需要手动实现#PostgreSQL:
-- 删除表
DROP TABLE IF EXISTS employees;
-- 删除表及相关对象
DROP TABLE IF EXISTS employees CASCADE; -- 删除依赖此表的对象
DROP TABLE IF EXISTS employees RESTRICT; -- 只有在没有依赖时才删除#4. 索引操作
#4.1 创建索引
#MySQL索引:
-- 普通索引
CREATE INDEX idx_last_name ON employees(last_name);
CREATE INDEX idx_name ON employees(last_name, first_name);
-- 唯一索引
CREATE UNIQUE INDEX idx_employee_email ON employees(email);
-- 全文索引(仅MyISAM和InnoDB)
CREATE FULLTEXT INDEX idx_description ON products(description);
-- 空间索引(仅MyISAM)
CREATE SPATIAL INDEX idx_location ON locations(location);
-- 创建表时定义索引
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
category_id INT,
INDEX idx_category (category_id),
UNIQUE INDEX idx_sku (sku)
) ENGINE=InnoDB;#PostgreSQL索引:
-- 普通索引
CREATE INDEX idx_last_name ON employees(last_name);
CREATE INDEX idx_name ON employees(last_name, first_name);
-- 唯一索引
CREATE UNIQUE INDEX idx_employee_email ON employees(email);
-- 部分索引
CREATE INDEX idx_active_employees ON employees(employee_id)
WHERE status = 'active';
-- 表达式索引
CREATE INDEX idx_upper_name ON employees(UPPER(last_name));
-- 不同类型的索引
CREATE INDEX idx_gin_json ON documents USING GIN (metadata); -- GIN索引用于JSON
CREATE INDEX idx_gist_geom ON locations USING GIST (geom); -- GiST索引用于几何数据
CREATE INDEX idx_hash_id ON employees USING HASH (employee_id); -- Hash索引
-- 并发创建索引(不锁定表)
CREATE INDEX CONCURRENTLY idx_large_table ON large_table(column_name);#4.2 删除索引
#MySQL:
-- 删除索引
DROP INDEX idx_last_name ON employees;#PostgreSQL:
-- 删除索引
DROP INDEX idx_last_name;
-- 或者
DROP INDEX IF EXISTS idx_last_name CASCADE;#5. 视图操作
#5.1 创建视图
#MySQL视图:
-- 创建简单视图
CREATE VIEW active_employees AS
SELECT employee_id, first_name, last_name, email, salary
FROM employees
WHERE status = 'active';
-- 创建复杂视图
CREATE VIEW department_summary AS
SELECT
d.department_name,
COUNT(e.employee_id) as employee_count,
AVG(e.salary) as avg_salary,
MIN(e.hire_date) as earliest_hire
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
-- 创建可更新视图
CREATE VIEW high_earners AS
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE salary > 80000
WITH CHECK OPTION; -- 确保更新时仍满足条件#PostgreSQL视图:
-- 创建简单视图
CREATE VIEW active_employees AS
SELECT employee_id, first_name, last_name, email, salary
FROM employees
WHERE status = 'active';
-- 创建复杂视图
CREATE VIEW department_summary AS
SELECT
d.department_name,
COUNT(e.employee_id) as employee_count,
AVG(e.salary) as avg_salary,
MIN(e.hire_date) as earliest_hire
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
-- 创建物化视图(PostgreSQL特色)
CREATE MATERIALIZED VIEW monthly_sales_summary AS
SELECT
DATE_TRUNC('month', sale_date) as month,
SUM(amount) as total_sales,
COUNT(*) as transaction_count
FROM sales
GROUP BY DATE_TRUNC('month', sale_date);
-- 刷新物化视图
REFRESH MATERIALIZED VIEW monthly_sales_summary;#5.2 修改和删除视图
#MySQL:
-- 修改视图
CREATE OR REPLACE VIEW active_employees AS
SELECT employee_id, first_name, last_name, email, salary, hire_date
FROM employees
WHERE status = 'active';
-- 删除视图
DROP VIEW IF EXISTS active_employees;#PostgreSQL:
-- 修改视图
CREATE OR REPLACE VIEW active_employees AS
SELECT employee_id, first_name, last_name, email, salary, hire_date
FROM employees
WHERE status = 'active';
-- 删除视图
DROP VIEW IF EXISTS active_employees CASCADE;#6. 约束操作
#6.1 MySQL约束
-- 创建带约束的表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL DEFAULT (CURRENT_DATE),
total_amount DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'processing', 'completed', 'cancelled') DEFAULT 'pending',
notes TEXT,
-- 约束定义
CONSTRAINT chk_amount CHECK (total_amount > 0),
CONSTRAINT chk_date CHECK (order_date <= CURRENT_DATE),
CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- 添加约束
ALTER TABLE orders ADD CONSTRAINT chk_status CHECK (status IN ('pending', 'processing', 'completed', 'cancelled'));
-- 删除约束
ALTER TABLE orders DROP CHECK chk_status;#6.2 PostgreSQL约束
-- 创建带约束的表
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
total_amount NUMERIC(10, 2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'cancelled')),
notes TEXT,
-- 约束定义
CONSTRAINT chk_amount CHECK (total_amount > 0),
CONSTRAINT chk_date CHECK (order_date <= CURRENT_DATE),
CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
ON DELETE RESTRICT
);
-- 添加约束
ALTER TABLE orders ADD CONSTRAINT chk_status
CHECK (status IN ('pending', 'processing', 'completed', 'cancelled'));
-- 删除约束
ALTER TABLE orders DROP CONSTRAINT chk_status;#7. 模式(Schema)操作
#7.1 PostgreSQL模式操作
-- 创建模式
CREATE SCHEMA sales_schema AUTHORIZATION sales_manager;
-- 在特定模式中创建表
CREATE TABLE sales_schema.monthly_reports (
report_id SERIAL PRIMARY KEY,
report_month DATE NOT NULL,
revenue NUMERIC(15, 2)
);
-- 查看所有模式
SELECT schema_name FROM information_schema.schemata;
-- 删除模式
DROP SCHEMA IF EXISTS sales_schema CASCADE;#8. DDL最佳实践
#8.1 设计原则
- 命名规范:
-- 好的命名示例
CREATE TABLE user_accounts (
account_id SERIAL PRIMARY KEY,
created_timestamp TIMESTAMP WITH TIME ZONE
);
-- 约束命名
CONSTRAINT fk_user_orders FOREIGN KEY (user_id) REFERENCES users(user_id)
CONSTRAINT chk_positive_amount CHECK (amount > 0)- 数据类型选择:
-- 选择合适的数据类型
-- 金额:NUMERIC/DECIMAL(避免浮点精度问题)
-- 标识符:SERIAL/INTEGER(根据数据量选择)
-- 文本:VARCHAR(根据最大长度)或 TEXT(长度不定)
-- 日期时间:TIMESTAMP WITH TIME ZONE(考虑时区)- 索引策略:
-- 避免过度索引
-- 为经常查询的列创建索引
-- 考虑复合索引的顺序
-- 定期审查和清理不必要的索引#8.2 性能考虑
-- MySQL性能优化
-- 使用适当的存储引擎(InnoDB用于事务)
-- 合理设置缓冲区大小
-- 定期分析表统计信息
-- PostgreSQL性能优化
-- 合理设置共享缓冲区
-- 使用适当的填充因子
-- 定期运行VACUUM和ANALYZE#8.3 安全考虑
-- 权限管理
-- 创建专用角色
CREATE ROLE app_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_reader;
GRANT USAGE ON SCHEMA public TO app_reader;
-- 敏感数据处理
-- 避免在表定义中包含敏感信息
-- 使用适当的访问控制#9. MySQL与PostgreSQL DDL差异总结
| 特性 | MySQL | PostgreSQL |
|---|---|---|
| 自增主键 | AUTO_INCREMENT | SERIAL |
| 默认值 | DEFAULT literal | DEFAULT expression |
| 分区表 | PARTITION BY | PARTITION BY + CREATE TABLE ... PARTITION OF |
| 物化视图 | 不支持 | 支持(MATERIALIZED VIEW) |
| 模式 | 使用数据库 | 独立的schema概念 |
| 数组类型 | 不支持 | 原生支持 |
| JSON支持 | JSON类型(5.7+) | JSON和JSONB类型 |
| 并发DDL | 有限 | CREATE INDEX CONCURRENTLY |
#相关教程
#总结
DDL是数据库设计和管理的核心工具,掌握各种DDL命令对于数据库管理员和开发人员都至关重要。MySQL和PostgreSQL在DDL实现上各有特色:
- MySQL:语法相对简单,适合快速开发,分区功能强大
- PostgreSQL:功能更丰富,标准兼容性更好,支持高级数据类型
选择合适的DDL策略和最佳实践,可以确保数据库结构的合理性、性能和可维护性。在实际应用中,应该根据具体的业务需求和团队技能来选择最适合的数据库系统。

