数据操作语言(DML)概述

数据操作语言(Data Manipulation Language,DML)是SQL语言的核心组成部分,用于操作数据库中的数据。DML命令允许用户对数据库中的记录进行增、删、改操作,是日常数据库操作中最常用的命令类型。

主要的DML命令包括:

  • INSERT - 向表中插入新记录
  • UPDATE - 修改表中的现有记录
  • DELETE - 从表中删除记录
  • REPLACE - 替换记录(插入或更新)
  • MERGE - 合并数据(MySQL 8.0+ 通过INSERT...ON DUPLICATE KEY UPDATE实现)

DML的重要性

DML在数据库管理中具有以下重要作用:

  • 数据维护 - 支持数据的日常增删改操作
  • 业务逻辑实现 - 实现应用程序的业务数据操作
  • 数据质量 - 通过约束和事务保证数据一致性
  • 性能优化 - 批量操作和索引优化提高操作效率
  • 数据安全 - 通过权限控制保护数据操作安全

INSERT语句

INSERT语句用于向表中插入新记录,是最基本的DML操作之一。

基本INSERT语法

SQL语法
-- 插入指定列
INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);

-- 插入所有列
INSERT INTO table_name
VALUES (value1, value2, value3, ...);

-- 插入多行数据
INSERT INTO table_name (column1, column2, ...)
VALUES 
    (value1, value2, ...),
    (value1, value2, ...),
    ...;

INSERT示例

SQL示例
-- 插入指定列(推荐方式)
INSERT INTO users (username, email, password, first_name, last_name)
VALUES ('john_doe', 'john@example.com', 'hashed_password', 'John', 'Doe');

-- 插入所有列(需要知道所有列的顺序)
INSERT INTO users
VALUES (NULL, 'jane_smith', 'jane@example.com', 'hashed_pass', 'Jane', 'Smith', '1990-05-15', NOW(), NOW(), 1);

-- 插入默认值
INSERT INTO products (product_name, price)
VALUES ('New Product', DEFAULT);

-- 插入NULL值
INSERT INTO users (username, email, password, birth_date)
VALUES ('test_user', 'test@example.com', 'pass', NULL);

-- 插入表达式结果
INSERT INTO sales (sale_date, amount, tax_rate, total_amount)
VALUES (CURDATE(), 100.00, 0.1, 100.00 * 1.1);

插入多行数据

SQL示例
INSERT INTO products (product_name, price, category_id)
VALUES 
    ('Laptop', 999.99, 1),
    ('Mouse', 29.99, 1),
    ('Keyboard', 79.99, 1),
    ('Monitor', 199.99, 1);

-- 批量插入用户数据
INSERT INTO users (username, email, password, first_name, last_name)
VALUES 
    ('alice_wonder', 'alice@example.com', 'pass1', 'Alice', 'Wonder'),
    ('bob_builder', 'bob@example.com', 'pass2', 'Bob', 'Builder'),
    ('charlie_brown', 'charlie@example.com', 'pass3', 'Charlie', 'Brown');

INSERT...SELECT语句

可以从另一个表中选择数据并插入到当前表中,常用于数据迁移、备份和报表生成。

SQL语法
INSERT INTO table1 (column1, column2, ...)
SELECT column1, column2, ...
FROM table2
WHERE condition;
SQL示例
-- 将活跃用户复制到新表
INSERT INTO active_users (user_id, username, email)
SELECT user_id, username, email
FROM users
WHERE is_active = 1;

-- 从订单表生成月度销售汇总
INSERT INTO monthly_sales (sale_month, total_sales, order_count)
SELECT 
    DATE_FORMAT(order_date, '%Y-%m'),
    SUM(total_amount),
    COUNT(*)
FROM orders
WHERE order_date >= '2023-01-01'
GROUP BY DATE_FORMAT(order_date, '%Y-%m');

-- 插入计算字段
INSERT INTO product_stats (product_id, avg_rating, review_count)
SELECT 
    product_id,
    AVG(rating),
    COUNT(*)
FROM reviews
GROUP BY product_id;

INSERT忽略重复

使用INSERT IGNORE可以忽略插入时的重复键错误,继续插入其他记录。

SQL示例
INSERT IGNORE INTO users (username, email, password)
VALUES ('john_doe', 'john@example.com', 'new_password');

-- 批量插入忽略重复
INSERT IGNORE INTO categories (category_name, description)
VALUES 
    ('Electronics', 'Electronic devices'),
    ('Books', 'All kinds of books'),
    ('Clothing', 'Fashion and apparel');

INSERT...ON DUPLICATE KEY UPDATE

当遇到重复键时更新记录而不是插入新记录,常用于"upsert"操作。

SQL示例
INSERT INTO users (username, email, login_count)
VALUES ('john_doe', 'john@example.com', 1)
ON DUPLICATE KEY UPDATE 
    login_count = login_count + 1,
    last_login = NOW();

-- 更新产品库存
INSERT INTO products (product_id, product_name, stock_quantity)
VALUES (123, 'Laptop', 10)
ON DUPLICATE KEY UPDATE 
    stock_quantity = stock_quantity + VALUES(stock_quantity),
    updated_at = NOW();

-- 使用VALUES()函数引用插入的值
INSERT INTO settings (setting_key, setting_value)
VALUES ('maintenance_mode', 'enabled')
ON DUPLICATE KEY UPDATE 
    setting_value = VALUES(setting_value);

获取插入的ID

在插入自增主键记录后,可以使用LAST_INSERT_ID()函数获取插入的ID。

SQL示例
INSERT INTO users (username, email, password)
VALUES ('new_user', 'new@example.com', 'password');

SELECT LAST_INSERT_ID();  -- 返回新插入用户的ID

-- 在应用程序中使用
-- $user_id = mysqli_insert_id($connection); // PHP
-- let user_id = result.insertId; // Node.js

UPDATE语句

UPDATE语句用于修改表中的现有记录,可以根据条件更新单个或多个记录。

基本UPDATE语法

SQL语法
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
重要安全提示:UPDATE语句如果没有WHERE条件,将会更新表中的所有记录!请务必小心使用。建议在执行UPDATE前先用SELECT验证WHERE条件。

UPDATE示例

SQL示例
-- 更新单个用户的邮箱
UPDATE users
SET email = 'new_email@example.com'
WHERE username = 'john_doe';

-- 更新多个列
UPDATE users
SET 
    first_name = 'Jonathan',
    last_name = 'Doerson',
    updated_at = NOW()
WHERE user_id = 1;

-- 基于表达式更新
UPDATE products
SET 
    price = price * 0.9,  -- 打9折
    stock_quantity = stock_quantity - 1
WHERE product_id = 5;

-- 批量更新
UPDATE users
SET is_active = 0
WHERE last_login < DATE_SUB(NOW(), INTERVAL 1 YEAR);

-- 使用CASE条件更新
UPDATE products
SET price = 
    CASE 
        WHEN category_id = 1 THEN price * 1.1  -- 电子产品涨价10%
        WHEN category_id = 2 THEN price * 0.9  -- 书籍降价10%
        ELSE price  -- 其他类别价格不变
    END;

UPDATE...JOIN语句

可以使用JOIN在UPDATE语句中基于其他表的值来更新数据。

SQL示例
-- 根据类别表更新产品价格
UPDATE products p
JOIN categories c ON p.category_id = c.category_id
SET p.price = p.price * 1.1  -- 涨价10%
WHERE c.category_name = 'Electronics';

-- 更新用户最后订单日期
UPDATE users u
JOIN (
    SELECT user_id, MAX(order_date) AS last_order_date
    FROM orders
    GROUP BY user_id
) o ON u.user_id = o.user_id
SET u.last_order_date = o.last_order_date;

-- 多表连接更新
UPDATE order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
SET oi.unit_price = p.price
WHERE o.status = 'pending';

UPDATE限制更新行数

SQL示例
-- 只更新前10条记录
UPDATE products
SET featured = 1
WHERE category_id = 1
ORDER BY price DESC
LIMIT 10;

-- 分批更新大量数据
UPDATE large_table
SET processed = 1
WHERE processed = 0
LIMIT 1000;

获取受影响的行数

执行UPDATE后,可以获取受影响的行数来确认操作结果。

SQL示例
UPDATE users 
SET last_login = NOW() 
WHERE user_id = 1;

-- 在应用程序中获取受影响行数
-- $affected_rows = mysqli_affected_rows($connection); // PHP
-- let affectedRows = result.affectedRows; // Node.js

SELECT ROW_COUNT();  -- 返回最近操作影响的行数

DELETE语句

DELETE语句用于从表中删除记录,可以根据条件删除单个或多个记录。

基本DELETE语法

SQL语法
DELETE FROM table_name
WHERE condition;
重要安全提示:DELETE语句如果没有WHERE条件,将会删除表中的所有记录!请务必小心使用。建议在执行DELETE前先用SELECT验证WHERE条件,并考虑使用事务或备份。

DELETE示例

SQL示例
-- 删除特定用户
DELETE FROM users
WHERE username = 'inactive_user';

-- 删除多条记录
DELETE FROM products
WHERE stock_quantity = 0;

-- 删除所有非活跃用户
DELETE FROM users
WHERE is_active = 0;

-- 删除过期数据
DELETE FROM session_data
WHERE expiry_date < NOW();

-- 使用子查询删除
DELETE FROM products
WHERE category_id IN (
    SELECT category_id FROM categories 
    WHERE is_active = 0
);

DELETE...JOIN语句

可以使用JOIN在DELETE语句中基于其他表的值来删除数据。

SQL示例
-- 删除没有订单的用户
DELETE u FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.order_id IS NULL;

-- 删除特定类别的产品
DELETE p FROM products p
JOIN categories c ON p.category_id = c.category_id
WHERE c.category_name = 'Discontinued';

-- 多表删除
DELETE o, oi
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date < '2020-01-01';

DELETE限制删除行数

SQL示例
-- 只删除前1000条记录
DELETE FROM audit_logs
WHERE log_date < DATE_SUB(NOW(), INTERVAL 1 YEAR)
ORDER BY log_date ASC
LIMIT 1000;

-- 分批删除大量数据
DELETE FROM large_table
WHERE condition
LIMIT 1000;  -- 重复执行直到没有更多数据

REPLACE语句

REPLACE语句类似于INSERT,但如果记录已存在(基于主键或唯一索引)则先删除再插入。

SQL语法
REPLACE INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
SQL示例
-- 替换用户信息
REPLACE INTO users (user_id, username, email, password)
VALUES (1, 'john_doe', 'new_email@example.com', 'new_password');

-- 如果user_id=1存在则替换,否则插入
REPLACE INTO settings (setting_key, setting_value)
VALUES ('site_name', 'My New Site');
REPLACE vs INSERT...ON DUPLICATE KEY UPDATE:
  • REPLACE会删除旧记录并插入新记录,自增ID会改变
  • INSERT...ON DUPLICATE KEY UPDATE会更新现有记录,自增ID不变
  • REPLACE会触发DELETE和INSERT触发器
  • INSERT...ON DUPLICATE KEY UPDATE只触发UPDATE触发器
  • 通常推荐使用INSERT...ON DUPLICATE KEY UPDATE

事务中的DML操作

在事务中执行DML操作可以保证数据的一致性,要么全部成功,要么全部失败。

SQL示例
-- 开始事务
START TRANSACTION;

-- 执行多个DML操作
INSERT INTO orders (user_id, total_amount, status)
VALUES (1, 99.99, 'pending');

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (LAST_INSERT_ID(), 5, 1, 99.99);

UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = 5;

-- 提交事务
COMMIT;

-- 如果发生错误,可以回滚
-- ROLLBACK;

性能优化技巧

批量操作

SQL示例
-- 批量插入(比单条插入快很多)
INSERT INTO products (product_name, price)
VALUES 
    ('Product 1', 10.00),
    ('Product 2', 20.00),
    ('Product 3', 30.00),
    ...  -- 可以插入数百甚至数千条
    ('Product N', 100.00);

-- 分批更新大量数据
UPDATE large_table
SET processed = 1
WHERE id BETWEEN 1 AND 1000;

UPDATE large_table
SET processed = 1
WHERE id BETWEEN 1001 AND 2000;
-- 继续直到完成

使用索引优化

SQL示例
-- 为WHERE条件中的列创建索引
CREATE INDEX idx_user_email ON users (email);
CREATE INDEX idx_product_category ON products (category_id);

-- 现在这些查询会更快
UPDATE users SET last_login = NOW() WHERE email = 'user@example.com';
DELETE FROM products WHERE category_id = 5;

错误处理和最佳实践

常见错误处理

SQL示例
-- 检查重复键错误
INSERT IGNORE INTO users (username, email)
VALUES ('existing_user', 'user@example.com');

-- 使用事务保证数据一致性
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE account_id = 1;
UPDATE account SET balance = balance + 100 WHERE account_id = 2;
COMMIT;

最佳实践

  • 总是使用WHERE条件 - 避免意外更新或删除所有记录
  • 先SELECT后UPDATE/DELETE - 验证WHERE条件是否正确
  • 使用事务 - 保证相关操作的原子性
  • 批量操作 - 减少数据库往返次数
  • 备份重要数据 - 在执行大规模更新或删除前备份数据
  • 测试环境验证 - 在生产环境执行前先在测试环境验证
  • 监控性能 - 监控DML操作的性能影响

实际应用场景

用户注册流程

SQL示例
-- 用户注册
START TRANSACTION;

INSERT INTO users (username, email, password, first_name, last_name)
VALUES ('newuser123', 'newuser@example.com', 'hashed_password', 'John', 'Doe');

SET @new_user_id = LAST_INSERT_ID();

-- 创建用户配置
INSERT INTO user_settings (user_id, theme, language, notifications_enabled)
VALUES (@new_user_id, 'light', 'en', 1);

-- 记录注册事件
INSERT INTO user_events (user_id, event_type, event_data)
VALUES (@new_user_id, 'registration', '{"source": "web"}');

COMMIT;

订单处理流程

SQL示例
-- 创建订单
START TRANSACTION;

INSERT INTO orders (user_id, total_amount, status, shipping_address)
VALUES (123, 199.98, 'pending', '123 Main St');

SET @new_order_id = LAST_INSERT_ID();

-- 添加订单项
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES 
    (@new_order_id, 5, 1, 99.99),
    (@new_order_id, 8, 2, 49.99);

-- 更新库存
UPDATE products 
SET stock_quantity = stock_quantity - 1 
WHERE product_id = 5;

UPDATE products 
SET stock_quantity = stock_quantity - 2 
WHERE product_id = 8;

COMMIT;

下一步学习

掌握了数据操作语言后,您可以继续学习: