数据操作语言

数据操作语言(DML)是SQL语言中用于操作数据库中数据的部分,主要包括插入(INSERT)、更新(UPDATE)和删除(DELETE)数据等操作。本章将详细介绍MySQL中数据操作语言的使用方法,以及MySQL 8.0引入的一些新特性。

5.1 插入数据

插入数据是向数据库表中添加新记录的操作。MySQL提供了多种插入数据的方式,包括为表的所有字段插入数据、为表的指定字段插入数据、同时插入多条记录以及将查询结果插入表中等。

5.1.1 为表的所有字段插入数据

当为表的所有字段插入数据时,可以不指定字段名,但需要保证值的顺序与表中字段的顺序一致。

INSERT INTO 表名 VALUES (值1, 值2, ..., 值n);

示例:为表的所有字段插入数据

-- 假设有一个employees表,包含id, name, age, department, salary五个字段
INSERT INTO employees VALUES (1, '张三', 30, '技术部', 8000);

-- 可以使用DEFAULT关键字为自增字段或有默认值的字段指定默认值
INSERT INTO employees VALUES (DEFAULT, '李四', 25, '市场部', 6000);

-- 对于字符串值,需要使用单引号或双引号括起来
INSERT INTO employees VALUES (DEFAULT, "王五", 35, "人事部", 7000);

提示:虽然可以不指定字段名插入数据,但为了代码的可读性和避免因表结构变化导致的错误,建议始终指定字段名。

5.1.2 为表的指定字段插入数据

在实际应用中,经常需要只为表的部分字段插入数据,而其他字段使用默认值或NULL值。

INSERT INTO 表名 (字段1, 字段2, ..., 字段m) VALUES (值1, 值2, ..., 值m);

示例:为表的指定字段插入数据

-- 只为name, department和salary字段插入数据,其他字段使用默认值
INSERT INTO employees (name, department, salary) VALUES ('赵六', '财务部', 6500);

-- 可以任意指定字段的顺序
INSERT INTO employees (department, name, salary, age) VALUES ('技术部', '孙七', 9000, 28);

-- 可以插入NULL值(如果字段允许NULL)
INSERT INTO employees (name, department, salary, age) VALUES ('周八', '市场部', NULL, 26);

5.1.3 同时插入多条记录

MySQL支持在一条INSERT语句中同时插入多条记录,这比多次执行单条INSERT语句更高效。

INSERT INTO 表名 (字段1, 字段2, ..., 字段m) VALUES
(值1, 值2, ..., 值m),
(值1, 值2, ..., 值m),
...
(值1, 值2, ..., 值m);

示例:同时插入多条记录

-- 同时插入3条记录
INSERT INTO employees (name, age, department, salary) VALUES
('吴九', 32, '技术部', 8500),
('郑十', 29, '市场部', 6800),
('钱十一', 31, '财务部', 7200);

-- 不指定字段名,同时插入多条记录
INSERT INTO departments VALUES
(DEFAULT, '研发部'),
(DEFAULT, '测试部'),
(DEFAULT, '运维部');

提示:同时插入多条记录可以减少与数据库的交互次数,提高插入效率。特别是在插入大量数据时,这种方式的性能优势更加明显。

5.1.4 将查询结果插入表中

MySQL支持将一个查询的结果插入到另一个表中,这在数据迁移、备份和报表生成等场景中非常有用。

INSERT INTO 目标表 (字段1, 字段2, ..., 字段m)
SELECT 字段1, 字段2, ..., 字段m
FROM 源表
WHERE 条件;

示例:将查询结果插入表中

-- 假设有一个employees表和一个high_salary_employees表
-- 创建high_salary_employees表
CREATE TABLE high_salary_employees (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    department VARCHAR(50),
    salary DECIMAL(10,2)
);

-- 将employees表中工资大于8000的员工插入到high_salary_employees表
INSERT INTO high_salary_employees (id, name, department, salary)
SELECT id, name, department, salary
FROM employees
WHERE salary > 8000;

-- 如果两个表的结构相同,可以省略字段列表
-- 复制整个表的数据
CREATE TABLE employees_backup LIKE employees;
INSERT INTO employees_backup
SELECT * FROM employees;

-- 插入计算结果
-- 假设有一个orders表和一个monthly_sales表
-- 创建monthly_sales表
CREATE TABLE monthly_sales (
    year INT,
    month INT,
    total_sales DECIMAL(12,2)
);

-- 计算每个月的销售总额并插入到monthly_sales表
INSERT INTO monthly_sales (year, month, total_sales)
SELECT YEAR(order_date), MONTH(order_date), SUM(amount)
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date);

5.2 更新数据

更新数据是修改表中现有记录的操作。在MySQL中,使用UPDATE语句来更新数据。

UPDATE 表名
SET 字段1 = 值1, 字段2 = 值2, ..., 字段m = 值m
[WHERE 条件]
[ORDER BY 排序字段]
[LIMIT 数量];

其中:

  • 表名:要更新数据的表名
  • SET:指定要更新的字段和新值
  • WHERE:可选,指定更新条件,只有满足条件的记录才会被更新
  • ORDER BY:可选,指定更新记录的顺序
  • LIMIT:可选,限制更新记录的数量

示例:更新数据

-- 更新单个字段
UPDATE employees
SET salary = 8500
WHERE id = 1;

-- 更新多个字段
UPDATE employees
SET department = '研发部', salary = 9000
WHERE name = '张三';

-- 使用WHERE条件更新多条记录
UPDATE employees
SET salary = salary * 1.1
WHERE department = '技术部';

-- 使用表达式更新字段值
UPDATE employees
SET hire_date = CURRENT_DATE()
WHERE id = 5;

-- 使用子查询更新数据
-- 假设有一个departments表,包含department_id和manager_id字段
UPDATE employees e
SET manager_id = (
    SELECT manager_id
    FROM departments d
    WHERE d.department_name = e.department
)
WHERE department = '技术部';

-- 使用ORDER BY和LIMIT限制更新的记录
UPDATE employees
SET salary = salary * 1.05
ORDER BY hire_date DESC
LIMIT 10;  -- 只更新最近入职的10名员工的工资

警告:在使用UPDATE语句时,如果不指定WHERE条件,将会更新表中的所有记录。请务必谨慎使用,最好在执行前先使用SELECT语句验证条件。

5.3 删除数据

删除数据是从表中移除记录的操作。在MySQL中,使用DELETE语句来删除数据。

DELETE FROM 表名
[WHERE 条件]
[ORDER BY 排序字段]
[LIMIT 数量];

其中:

  • 表名:要删除数据的表名
  • WHERE:可选,指定删除条件,只有满足条件的记录才会被删除
  • ORDER BY:可选,指定删除记录的顺序
  • LIMIT:可选,限制删除记录的数量

示例:删除数据

-- 删除单条记录
DELETE FROM employees
WHERE id = 10;

-- 删除满足条件的多条记录
DELETE FROM employees
WHERE department = '行政部';

-- 使用复杂的WHERE条件
DELETE FROM orders
WHERE order_date < '2023-01-01' AND status = 'cancelled';

-- 使用ORDER BY和LIMIT限制删除的记录
DELETE FROM logs
ORDER BY log_date ASC
LIMIT 1000;  -- 删除最早的1000条日志记录

-- 使用子查询删除数据
DELETE FROM employees
WHERE department IN (
    SELECT department_name
    FROM departments
    WHERE status = 'inactive'
);

警告:在使用DELETE语句时,如果不指定WHERE条件,将会删除表中的所有记录。请务必谨慎使用,最好在执行前先使用SELECT语句验证条件。

5.3.1 删除没有被关联的表

对于没有被其他表关联的表,可以直接使用DELETE语句删除数据。

示例:删除没有被关联的表的数据

-- 假设有一个独立的表,没有外键关联
DELETE FROM temp_data
WHERE create_time < '2023-01-01';

-- 删除整个表的数据(但保留表结构)
TRUNCATE TABLE temp_data;  -- 比DELETE FROM更高效

-- 或者
DELETE FROM temp_data;

5.3.2 删除被其他表关联的主表

如果要删除的数据所在的表是主表(被其他表通过外键关联),则需要特别注意,因为外键约束会防止删除被引用的记录。

有几种方法可以解决这个问题:

  1. 先删除从表中关联的记录,再删除主表中的记录
  2. 修改外键约束,设置ON DELETE CASCADE(级联删除)
  3. 暂时禁用外键约束,删除数据后再启用

示例:删除被其他表关联的主表的数据

-- 假设有一个departments表(主表)和一个employees表(从表),employees表有一个外键指向departments表

-- 方法1:先删除从表中的关联记录,再删除主表中的记录
DELETE FROM employees WHERE department_id = 5;
DELETE FROM departments WHERE department_id = 5;

-- 方法2:修改外键约束,设置ON DELETE CASCADE
-- 首先删除现有的外键约束
ALTER TABLE employees DROP FOREIGN KEY fk_department;
-- 然后添加新的外键约束,设置级联删除
ALTER TABLE employees
ADD CONSTRAINT fk_department
FOREIGN KEY (department_id) REFERENCES departments(department_id) ON DELETE CASCADE;
-- 现在可以直接删除主表中的记录,从表中关联的记录会自动删除
DELETE FROM departments WHERE department_id = 6;

-- 方法3:暂时禁用外键约束
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM departments WHERE department_id = 7;
-- 删除完成后,重新启用外键约束
SET FOREIGN_KEY_CHECKS = 1;
-- 注意:这种方法可能会导致数据不一致,谨慎使用

提示:在删除大量数据时,TRUNCATE TABLE语句比DELETE语句更高效,因为TRUNCATE TABLE不会记录日志,也不会触发触发器。但TRUNCATE TABLE会删除表中的所有数据,且不能使用WHERE条件。

5.4 为表增加计算列

MySQL 5.7及以上版本支持计算列(Generated Columns),计算列的值是根据其他列的值计算出来的。计算列可以分为虚拟计算列(Virtual)和存储计算列(Stored)两种类型:

  • 虚拟计算列(Virtual):不存储在磁盘上,只有在查询时才计算,不占用存储空间
  • 存储计算列(Stored):存储在磁盘上,更新时会重新计算,占用存储空间,但查询性能更好
-- 创建表时定义计算列
CREATE TABLE 表名 (
    字段1 数据类型,
    字段2 数据类型,
    ...
    计算列名 数据类型 GENERATED ALWAYS AS (表达式) [VIRTUAL | STORED]
);

示例:为表增加计算列

-- 创建包含计算列的表
CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    price DECIMAL(10,2),
    quantity INT,
    -- 虚拟计算列:计算总价值
    total_value DECIMAL(12,2) GENERATED ALWAYS AS (price * quantity) VIRTUAL,
    -- 存储计算列:计算折扣价格(假设折扣为90%)
    discounted_price DECIMAL(10,2) GENERATED ALWAYS AS (price * 0.9) STORED
);

-- 插入数据(不需要为计算列提供值)
INSERT INTO products (name, price, quantity) VALUES ('笔记本电脑', 5999.00, 10);

-- 查询数据(可以直接使用计算列)
SELECT id, name, price, quantity, total_value, discounted_price FROM products;

-- 输出示例
+----+--------------+---------+----------+-------------+------------------+
| id | name         | price   | quantity | total_value | discounted_price |
+----+--------------+---------+----------+-------------+------------------+
|  1 | 笔记本电脑   | 5999.00 |       10 |    59990.00 |          5399.10 |
+----+--------------+---------+----------+-------------+------------------+

-- 修改表,添加计算列
ALTER TABLE orders
ADD total_amount DECIMAL(12,2) GENERATED ALWAYS AS (unit_price * quantity) VIRTUAL;

-- 修改计算列的定义
-- 首先删除现有的计算列
ALTER TABLE products DROP COLUMN total_value;
-- 然后添加新的计算列
ALTER TABLE products
ADD total_value DECIMAL(12,2) GENERATED ALWAYS AS (price * quantity * 1.13) VIRTUAL;  -- 包含13%的税

提示:计算列可以使用索引,这对于基于计算结果的查询非常有用。但是,存储计算列会增加数据更新的开销,而虚拟计算列会增加查询的开销。在选择计算列类型时,需要根据具体的应用场景进行权衡。

5.5 MySQL 8.0的新特性——DDL的原子化

MySQL 8.0引入了原子DDL(Atomic DDL)功能,确保数据定义语言(DDL)语句的执行是原子的:要么全部成功,要么全部失败,不会出现部分执行的情况。这大大提高了数据库的可靠性和一致性。

5.5.1 原子DDL的优势

  • 提高数据一致性:确保DDL操作不会导致数据库处于不一致的状态
  • 简化错误处理:只需要处理整个操作成功或失败的情况,不需要处理部分成功的复杂情况
  • 提高可靠性:在系统崩溃或断电等异常情况下,数据库能够恢复到一致的状态

5.5.2 原子DDL支持的操作类型

MySQL 8.0的原子DDL功能支持以下类型的操作:

  • 表操作:CREATE、ALTER、DROP表等
  • 索引操作:CREATE、ALTER、DROP索引等
  • 视图操作:CREATE、ALTER、DROP视图等
  • 触发器操作:CREATE、ALTER、DROP触发器等
  • 存储过程和函数操作:CREATE、ALTER、DROP存储过程和函数等

示例:原子DDL操作

-- 开始事务
START TRANSACTION;

-- 执行一个复杂的DDL操作
ALTER TABLE employees
    ADD COLUMN department_id INT,
    ADD CONSTRAINT fk_department FOREIGN KEY (department_id) REFERENCES departments(id),
    MODIFY COLUMN salary DECIMAL(12,2);

-- 如果执行成功,提交事务
COMMIT;
-- 如果执行失败,回滚事务
-- ROLLBACK;

-- 原子DDL操作示例:创建表并同时创建索引和触发器
START TRANSACTION;

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT,
    order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10,2),
    INDEX idx_customer_id (customer_id)
);

CREATE TRIGGER before_order_insert
BEFORE INSERT ON orders
FOR EACH ROW
SET NEW.order_date = CURRENT_TIMESTAMP;

-- 如果所有操作都成功,提交事务;如果任何操作失败,所有操作都会回滚
COMMIT;

提示:原子DDL功能依赖于MySQL的数据字典,这也是MySQL 8.0的一个重要新特性。数据字典将元数据(如表、索引、视图等的定义)存储在InnoDB引擎的系统表中,而不是像以前那样存储在文件系统中。