1. SQL简介
SQL(Structured Query Language,结构化查询语言)是一种专门用于管理关系型数据库的标准编程语言。它由IBM在20世纪70年代开发,现在已经成为数据库管理系统的国际标准语言。
SQL之所以如此重要,是因为它提供了一套统一的方式来与数据库进行交互。无论你使用的是MySQL、SQL Server、PostgreSQL还是Oracle,SQL的基本语法都是相通的,这使得开发者可以轻松地在不同的数据库系统之间切换。
SQL的主要功能包括:
- 查询数据(SELECT): 从数据库中检索数据,这是SQL最常用的功能。你可以查询单个表的数据,也可以跨多个表进行复杂查询
- 插入数据(INSERT): 将新数据添加到数据库表中。可以一次插入一条记录,也可以批量插入多条记录
- 更新数据(UPDATE): 修改数据库中已存在的数据。可以更新单个字段的值,也可以同时更新多个字段
- 删除数据(DELETE): 从数据库中移除不再需要的数据。可以删除特定条件的记录,也可以删除所有记录
- 创建和修改数据库结构(DDL): 定义数据库、表、视图、索引等数据库对象的结构。包括创建、修改和删除这些对象的操作
SQL语言具有以下特点:
- 声明式语言: 你只需要告诉数据库"要什么",而不需要告诉它"怎么做"
- 面向集合: SQL操作通常作用于整个数据集,而不是单条记录
- 非过程化: 不需要编写复杂的控制流程,SQL会自动优化执行路径
- 标准化: 大部分SQL语法在不同数据库中都是兼容的
2. 基本概念
2.1 数据库表
数据库表是数据存储的基本单位,它以一种类似Excel表格的结构来组织数据。表由行(记录)和列(字段)组成,每一行代表一条完整的数据记录,每一列代表一种特定的数据属性。
表名: Students(学生表)
+----+--------+------+----------------------+
| ID | Name | Age | Email |
+----+--------+------+----------------------+
| 1 | 张三 | 20 | zhangsan@example.com |
| 2 | 李四 | 21 | lisi@example.com |
| 3 | 王五 | 22 | wangwu@example.com |
+----+--------+------+----------------------+
在这个示例中:
- Students 是表的名称,用于存储学生信息
- ID、Name、Age、Email 是列名,分别代表学号、姓名、年龄和邮箱
- 每一行代表一个学生的完整信息
- 表的第一行通常是列名,用于描述每一列的含义
设计良好的表结构应该遵循以下原则:
- 每一列应该具有单一明确的意义
- 每一行的数据应该是完整且独立的
- 避免在表中存储重复数据
- 为每个表设置主键以唯一标识每一行
2.2 数据类型
选择合适的数据类型对于数据库性能和数据完整性至关重要。不同的数据类型占用不同的存储空间,并且支持不同的操作。
-
INT(整数): 用于存储整数值,如年龄、数量、ID等。根据数据库的不同,INT类型可能有不同的取值范围,通常为-2,147,483,648到2,147,483,647
-
VARCHAR(n)(可变长度字符串): 用于存储可变长度的文本数据,如姓名、地址等。n表示最大字符数,实际占用的存储空间取决于实际存储的字符数。例如,VARCHAR(50)最多可以存储50个字符
-
CHAR(n)(固定长度字符串): 用于存储固定长度的文本数据。无论实际存储的字符数是多少,都会占用n个字符的存储空间。CHAR类型适合存储长度固定的数据,如电话号码、邮政编码等
-
DECIMAL(m,d)(精确数值): 用于存储精确的小数,m表示总位数,d表示小数位数。例如,DECIMAL(10,2)表示总共10位数字,其中2位小数,可以存储范围从-99999999.99到99999999.99的数值。这种类型适合存储金额、比例等需要精确计算的数据
-
DATE(日期): 用于存储日期值,格式通常为YYYY-MM-DD,如2026-03-11。可以存储从1000-01-01到9999-12-31之间的日期
-
DATETIME(日期和时间): 用于存储日期和时间信息,格式通常为YYYY-MM-DD HH:MM:SS。例如,2026-03-11 14:30:00表示2026年3月11日下午2点30分
-
BOOLEAN(布尔值): 用于存储真或假的逻辑值,通常用于表示开关状态、是否有效等二元选择
选择数据类型时需要考虑以下因素:
- 数据的实际范围和精度要求
- 存储空间的考虑
- 预期的查询类型和索引需求
- 不同数据库系统的具体实现
3. SQL语句分类
SQL语句按照功能可以分为三大类:DDL(数据定义语言)、DML(数据操作语言)和DQL(数据查询语言)。此外,还有DCL(数据控制语言)用于权限管理。
3.1 DDL(数据定义语言)
DDL语句用于定义和修改数据库的结构,包括创建、修改和删除数据库对象。这些操作会影响数据库的模式(Schema),通常由数据库管理员或开发者在应用设计阶段执行。
sql
-- 创建数据库
-- 在开始使用数据库之前,首先需要创建一个数据库容器
CREATE DATABASE mydb;
-- 创建表
-- 定义表的结构,包括列名、数据类型和各种约束
CREATE TABLE Students (
ID INT PRIMARY KEY, -- ID是主键,唯一标识每个学生
Name VARCHAR(50) NOT NULL, -- 姓名不能为空
Age INT, -- 年龄可以是整数
Email VARCHAR(100) UNIQUE, -- 邮箱地址必须唯一
CreateTime DATETIME DEFAULT NOW() -- 创建时间默认为当前时间
);
-- 删除表
-- 永久删除表及其所有数据,此操作不可恢复
DROP TABLE Students;
-- 修改表结构
-- 可以添加、删除或修改表的列
ALTER TABLE Students ADD COLUMN Gender VARCHAR(10); -- 添加性别列
ALTER TABLE Students MODIFY COLUMN Age INT NOT NULL; -- 修改年龄列为必填
ALTER TABLE Students DROP COLUMN CreateTime; -- 删除创建时间列
DDL操作需要谨慎,因为它们会直接影响数据库结构。在生产环境中,通常需要在执行DDL操作前备份数据,并在测试环境中验证。
3.2 DML(数据操作语言)
DML语句用于操作表中的数据,包括插入、更新和删除记录。这些是日常开发中最常用的SQL操作。
sql
-- 插入数据
-- 将新的学生记录添加到表中
INSERT INTO Students (ID, Name, Age, Email)
VALUES (1, '张三', 20, 'zhangsan@example.com');
-- 插入数据时省略某些列
-- 如果列有默认值或允许NULL,可以省略
INSERT INTO Students (ID, Name, Email)
VALUES (2, '李四', 'lisi@example.com');
-- 批量插入
-- 一次插入多条记录,提高效率
INSERT INTO Students (ID, Name, Age)
VALUES
(3, '王五', 22),
(4, '赵六', 21),
(5, '孙七', 23);
-- 更新数据
-- 修改表中已存在的记录,通常需要使用WHERE子句来限定范围
UPDATE Students SET Age = 21 WHERE ID = 1;
-- 同时更新多个字段
UPDATE Students
SET Age = 22, Email = 'newemail@example.com'
WHERE Name = '张三';
-- 批量更新
-- 更新所有年龄大于20岁的学生
UPDATE Students SET Age = Age + 1 WHERE Age > 20;
-- 删除数据
-- 从表中移除记录,同样需要使用WHERE子句来限定范围
DELETE FROM Students WHERE ID = 3;
-- 批量删除
-- 删除所有年龄小于18岁的学生记录
DELETE FROM Students WHERE Age < 18;
-- 警告:删除所有数据
-- 如果不使用WHERE子句,会删除表中的所有记录
-- DELETE FROM Students;
在使用DML语句时,务必注意:
- UPDATE和DELETE操作必须使用WHERE子句,否则会影响所有记录
- 对于重要数据的修改操作,建议先备份数据
- 可以使用事务来确保多个相关操作的原子性
- 执行前可以先使用SELECT语句验证将要影响的记录
3.3 DQL(数据查询语言)
DQL语句用于从数据库中检索数据,这是SQL中最常用的功能。通过SELECT语句,你可以获取所需的信息,并进行排序、分组和筛选。
sql
-- 查询所有数据
-- 星号(*)表示选择所有列,但在生产环境中不推荐使用
SELECT * FROM Students;
-- 查询指定列
-- 只选择需要的列,可以提高查询效率并减少数据传输
SELECT Name, Age FROM Students;
-- 条件查询
-- 使用WHERE子句过滤记录
SELECT * FROM Students WHERE Age > 20;
-- 复杂条件查询
-- 使用AND、OR组合多个条件
SELECT * FROM Students
WHERE Age > 18 AND Age < 25
AND (Name LIKE '张%' OR Name LIKE '李%');
-- 排序
-- 使用ORDER BY对结果进行排序,DESC表示降序,ASC表示升序(默认)
SELECT * FROM Students ORDER BY Age DESC;
-- 多列排序
-- 先按年龄升序排序,年龄相同的按姓名降序排序
SELECT * FROM Students ORDER BY Age ASC, Name DESC;
-- 限制结果数量
-- 使用LIMIT限制返回的记录数,常用于分页查询
SELECT * FROM Students LIMIT 5;
-- 分页查询
-- OFFSET指定从第几条记录开始返回,常用于实现分页功能
-- 每页显示10条记录,获取第3页的数据
SELECT * FROM Students LIMIT 10 OFFSET 20;
-- 去重查询
-- 使用DISTINCT消除重复的记录
SELECT DISTINCT Age FROM Students;
-- 计算字段
-- 可以在查询中进行计算
SELECT Name, Age, Age + 5 AS FutureAge FROM Students;
-- 聚合查询
-- 对数据进行统计汇总
SELECT
COUNT(*) as TotalStudents,
AVG(Age) as AverageAge,
MAX(Age) as MaxAge,
MIN(Age) as MinAge
FROM Students;
查询优化技巧:
- 避免使用SELECT *,明确指定需要的列
- 为WHERE和JOIN中使用的列创建索引
- 合理使用LIMIT限制结果数量
- 在大数据量查询时考虑分页处理
- 避免在WHERE子句中对列使用函数
4. 高级查询
4.1 WHERE子句
WHERE子句用于过滤查询结果,只返回满足条件的记录。它是SQL查询中最常用的子句之一。
sql
-- 比较运算符
-- =: 等于
-- != 或 <>: 不等于
-- >: 大于
-- <: 小于
-- >=: 大于等于
-- <=: 小于等于
SELECT * FROM Students WHERE Age = 20;
SELECT * FROM Students WHERE Age >= 18;
SELECT * FROM Students WHERE Name != '张三';
-- 逻辑运算符
-- AND: 所有条件都必须满足
-- OR: 任一条件满足即可
-- NOT: 对条件取反
SELECT * FROM Students WHERE Age > 18 AND Age < 25;
SELECT * FROM Students WHERE Age = 20 OR Age = 21;
SELECT * FROM Students WHERE NOT (Age < 18);
-- 范围查询
-- BETWEEN: 在指定范围内(包含边界值)
SELECT * FROM Students WHERE Age BETWEEN 18 AND 22;
-- IN: 值在列表中
SELECT * FROM Students WHERE Age IN (18, 19, 20);
-- 模糊匹配
-- LIKE: 用于字符串匹配
-- %: 匹配任意长度的任意字符
-- _: 匹配单个字符
SELECT * FROM Students WHERE Name LIKE '张%'; -- 姓张的学生
SELECT * FROM Students WHERE Name LIKE '%三'; -- 名字以三结尾的学生
SELECT * FROM Students WHERE Name LIKE '张_'; -- 姓张且名字为两个字的学生
-- NULL值判断
-- IS NULL: 值为NULL
-- IS NOT NULL: 值不为NULL
SELECT * FROM Students WHERE Email IS NULL;
SELECT * FROM Students WHERE Email IS NOT NULL;
-- 子查询
-- WHERE子句中可以使用子查询
SELECT * FROM Students
WHERE Age > (SELECT AVG(Age) FROM Students);
4.2 聚合函数
聚合函数对一组值进行计算,返回单个值。常用于数据统计和分析。
sql
-- COUNT: 统计记录数
-- COUNT(*): 统计所有记录数
-- COUNT(column): 统计指定列非NULL的记录数
SELECT COUNT(*) FROM Students; -- 统计学生总数
SELECT COUNT(Email) FROM Students; -- 统计有邮箱的学生数
-- AVG: 计算平均值
SELECT AVG(Age) FROM Students; -- 计算平均年龄
-- MAX: 获取最大值
SELECT MAX(Age) FROM Students; -- 获取最大年龄
-- MIN: 获取最小值
SELECT MIN(Age) FROM Students; -- 获取最小年龄
-- SUM: 计算总和
SELECT SUM(Age) FROM Students; -- 计算年龄总和
-- 组合使用聚合函数
SELECT
COUNT(*) as 学生总数,
AVG(Age) as 平均年龄,
MAX(Age) as 最大年龄,
MIN(Age) as 最小年龄
FROM Students;
-- 与DISTINCT结合使用
SELECT COUNT(DISTINCT Age) FROM Students; -- 统计不同年龄的数量
聚合函数注意事项:
- 聚合函数忽略NULL值(除了COUNT(*))
- 在使用聚合函数时,其他非聚合列必须出现在GROUP BY子句中
- 可以给聚合结果起别名,使结果更易读
4.3 GROUP BY 分组
GROUP BY子句将查询结果按照一个或多个列进行分组,通常与聚合函数一起使用,用于对数据进行分类统计。
sql
-- 基本分组
-- 按年龄分组,统计每个年龄的学生数量
SELECT Age, COUNT(*) as Count
FROM Students
GROUP BY Age;
-- 多列分组
-- 按年龄和性别分组
SELECT Age, Gender, COUNT(*) as Count
FROM Students
GROUP BY Age, Gender;
-- 分组后使用多个聚合函数
SELECT Age,
COUNT(*) as 学生数量,
AVG(Age) as 平均年龄,
MAX(Age) as 最大年龄
FROM Students
GROUP BY Age;
-- HAVING子句
-- HAVING用于过滤分组后的结果,类似于WHERE,但作用于分组
-- 查找学生数量大于1的年龄组
SELECT Age, COUNT(*) as Count
FROM Students
GROUP BY Age
HAVING COUNT(*) > 1;
-- HAVING和WHERE的区别
-- WHERE在分组前过滤记录,HAVING在分组后过滤分组
-- 下面的查询先筛选年龄大于18的学生,再分组统计
SELECT Age, COUNT(*) as Count
FROM Students
WHERE Age > 18
GROUP BY Age
HAVING COUNT(*) > 1;
-- WITH ROLLUP
-- 生成分组的汇总行
SELECT Age, COUNT(*) as Count
FROM Students
GROUP BY Age WITH ROLLUP;
GROUP BY使用要点:
- SELECT中的非聚合列必须出现在GROUP BY中
- WHERE在GROUP BY之前执行,HAVING在GROUP BY之后执行
- 可以按多个列进行分组
- 分组列的顺序会影响结果的组织方式
4.4 连接查询
连接查询用于从多个相关表中检索数据。连接是关系型数据库的核心特性,它允许你通过表之间的关系来整合数据。
sql
-- 内连接(INNER JOIN)
-- 只返回两个表中匹配的记录
-- 例如:查询学生及其选课信息
SELECT s.Name, c.CourseName
FROM Students s
INNER JOIN Courses c ON s.ID = c.StudentID;
-- 左连接(LEFT JOIN)
-- 返回左表的所有记录,以及右表中匹配的记录
-- 如果右表没有匹配记录,则显示NULL
-- 例如:查询所有学生及其选课信息,包括没有选课的学生
SELECT s.Name, c.CourseName
FROM Students s
LEFT JOIN Courses c ON s.ID = c.StudentID;
-- 右连接(RIGHT JOIN)
-- 返回右表的所有记录,以及左表中匹配的记录
-- 如果左表没有匹配记录,则显示NULL
-- 例如:查询所有课程及其选修学生,包括没有学生选修的课程
SELECT s.Name, c.CourseName
FROM Students s
RIGHT JOIN Courses c ON s.ID = c.StudentID;
-- 全连接(FULL JOIN)
-- 返回两个表中的所有记录,无论是否匹配
-- 如果没有匹配记录,则显示NULL
-- 注意:MySQL不支持FULL JOIN,可以用UNION ALL模拟
SELECT s.Name, c.CourseName
FROM Students s
FULL JOIN Courses c ON s.ID = c.StudentID;
-- 自连接
-- 表与自身进行连接
-- 例如:查询学生及其同班同学
SELECT s1.Name as 学生姓名, s2.Name as 同班同学
FROM Students s1
INNER JOIN Students s2 ON s1.ClassID = s2.ClassID AND s1.ID != s2.ID;
-- 多表连接
-- 可以连接多个表
SELECT s.Name, c.CourseName, t.TeacherName
FROM Students s
INNER JOIN Courses c ON s.ID = c.StudentID
INNER JOIN Teachers t ON c.TeacherID = t.ID;
-- 连接条件
-- 除了ON子句,还可以使用WHERE进行额外筛选
SELECT s.Name, c.CourseName
FROM Students s
INNER JOIN Courses c ON s.ID = c.StudentID
WHERE c.CourseName LIKE '数学%';
连接查询最佳实践:
- 始终为表设置别名,提高可读性
- 明确指定连接条件,避免隐式连接
- 根据需求选择合适的连接类型
- 注意NULL值的处理
- 合理使用索引提高连接性能
5. 约束
约束是数据库用来保证数据完整性和一致性的规则。它们确保存储在数据库中的数据符合业务规则和逻辑要求。
5.1 主键约束(PRIMARY KEY)
主键用于唯一标识表中的每一行记录。一个表只能有一个主键,但主键可以由多个列组成(复合主键)。
sql
-- 单列主键
CREATE TABLE Students (
ID INT PRIMARY KEY,
Name VARCHAR(50)
);
-- 复合主键
-- 当单个列无法唯一标识记录时使用
CREATE TABLE StudentCourses (
StudentID INT,
CourseID INT,
Score INT,
PRIMARY KEY (StudentID, CourseID)
);
-- 主键的特点:
-- 1. 值必须唯一,不能重复
-- 2. 值不能为NULL
-- 3. 一个表只能有一个主键
-- 4. 主键会自动创建索引,提高查询效率
5.2 外键约束(FOREIGN KEY)
外键用于建立表与表之间的关系,确保参照完整性。外键值必须是父表中存在的主键值。
sql
-- 创建外键约束
CREATE TABLE Courses (
CourseID INT PRIMARY KEY,
StudentID INT,
CourseName VARCHAR(100),
FOREIGN KEY (StudentID) REFERENCES Students(ID)
);
-- 外键的特点:
-- 1. 外键值必须在父表中存在(或为NULL)
-- 2. 可以定义级联操作(ON DELETE、ON UPDATE)
-- 3. 保证数据的参照完整性
-- 级联操作示例
CREATE TABLE Courses (
CourseID INT PRIMARY KEY,
StudentID INT,
CourseName VARCHAR(100),
FOREIGN KEY (StudentID) REFERENCES Students(ID)
ON DELETE CASCADE -- 删除学生时自动删除相关课程
ON UPDATE CASCADE -- 更新学生ID时自动更新相关课程
);
5.3 唯一约束(UNIQUE)
唯一约束确保列中的所有值都是唯一的,但允许NULL值(可以有多个NULL)。
sql
CREATE TABLE Students (
ID INT PRIMARY KEY,
Email VARCHAR(100) UNIQUE, -- 邮箱必须唯一
Phone VARCHAR(20) UNIQUE -- 电话号码必须唯一
);
-- 唯一约束与主键的区别:
-- 1. 主键不允许NULL,唯一约束允许NULL
-- 2. 一个表只能有一个主键,但可以有多个唯一约束
-- 3. 主键通常用于标识记录,唯一约束用于保证业务规则
5.4 非空约束(NOT NULL)
非空约束确保列不接受NULL值,必须提供具体的数据。
sql
CREATE TABLE Students (
ID INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL, -- 姓名不能为空
Age INT NOT NULL -- 年龄不能为空
);
-- NOT NULL约束:
-- 1. 强制要求提供值
-- 2. 插入或更新时必须指定该列的值
-- 3. 提高数据完整性
5.5 默认值约束(DEFAULT)
默认值约束为列指定默认值,当插入数据时如果没有为该列提供值,则使用默认值。
sql
CREATE TABLE Students (
ID INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
Age INT DEFAULT 18, -- 默认年龄为18
Status VARCHAR(20) DEFAULT 'active', -- 默认状态为active
CreateTime DATETIME DEFAULT CURRENT_TIMESTAMP -- 默认为当前时间
);
-- 默认值约束:
-- 1. 简化数据插入操作
-- 2. 确保列始终有值
-- 3. 可以使用函数作为默认值
6. 索引
索引是数据库中用于提高查询性能的数据结构。它类似于书的目录,可以帮助快速定位数据,而不需要扫描整个表。
sql
-- 创建普通索引
-- 为经常查询的列创建索引
CREATE INDEX idx_student_name ON Students(Name);
-- 创建复合索引
-- 为多个列的组合创建索引
CREATE INDEX idx_student_age_name ON Students(Age, Name);
-- 创建唯一索引
-- 确保列值的唯一性
CREATE UNIQUE INDEX idx_student_email ON Students(Email);
-- 创建全文索引
-- 用于文本搜索
CREATE FULLTEXT INDEX idx_student_description ON Students(Description);
-- 查看索引
SHOW INDEX FROM Students;
-- 删除索引
DROP INDEX idx_student_name ON Students;
-- 索引的优点:
-- 1. 大大提高查询速度
-- 2. 加速ORDER BY和GROUP BY操作
-- 3. 提高数据检索效率
-- 索引的缺点:
-- 1. 占用额外的存储空间
-- 2. 降低INSERT、UPDATE、DELETE的速度
-- 3. 需要维护索引结构
-- 索引使用建议:
-- 1. 为经常用于WHERE、JOIN、ORDER BY的列创建索引
-- 2. 不要为频繁更新的列创建过多索引
-- 3. 选择性高的列更适合创建索引
-- 4. 复合索引的列顺序很重要
-- 5. 定期分析索引使用情况,删除无用索引
7. 视图
视图是基于一个或多个表的虚拟表,它不存储实际数据,而是存储查询逻辑。视图可以简化复杂查询,提高数据安全性。
sql
-- 创建视图
-- 创建一个只包含成年学生的视图
CREATE VIEW AdultStudents AS
SELECT ID, Name, Age, Email
FROM Students
WHERE Age >= 18;
-- 使用视图
-- 像使用普通表一样使用视图
SELECT * FROM AdultStudents;
-- 创建复杂视图
-- 包含多个表的连接和聚合
CREATE VIEW StudentStatistics AS
SELECT
s.ID,
s.Name,
COUNT(c.CourseID) as CourseCount,
AVG(c.Score) as AverageScore
FROM Students s
LEFT JOIN Courses c ON s.ID = c.StudentID
GROUP BY s.ID, s.Name;
-- 创建可更新视图
-- 某些视图支持更新操作
CREATE VIEW StudentBasicInfo AS
SELECT ID, Name, Age
FROM Students;
-- 更新视图数据
UPDATE StudentBasicInfo SET Age = 21 WHERE ID = 1;
-- 查看视图定义
SHOW CREATE VIEW AdultStudents;
-- 删除视图
DROP VIEW AdultStudents;
-- 视图的优点:
-- 1. 简化复杂查询,提高可读性
-- 2. 提供数据安全性,限制用户访问敏感数据
-- 3. 封装业务逻辑,便于维护
-- 4. 实现数据逻辑独立性
-- 视图的限制:
-- 1. 某些视图不支持更新操作
-- 2. 视图查询可能有性能开销
-- 3. 复杂视图可能导致优化困难
8. 存储过程和函数
存储过程和函数是预编译的SQL代码块,可以重复执行,提高代码复用性和性能。
8.1 存储过程
存储过程是一组为了完成特定功能的SQL语句集合,可以接受参数并返回结果。
sql
-- 创建简单存储过程
-- 根据年龄查询学生
DELIMITER //
CREATE PROCEDURE GetStudentsByAge(IN minAge INT)
BEGIN
SELECT * FROM Students WHERE Age >= minAge;
END //
DELIMITER ;
-- 调用存储过程
CALL GetStudentsByAge(20);
-- 创建带输出参数的存储过程
-- 统计学生数量并返回结果
DELIMITER //
CREATE PROCEDURE GetStudentCount(OUT totalCount INT)
BEGIN
SELECT COUNT(*) INTO totalCount FROM Students;
END //
DELIMITER ;
-- 调用带输出参数的存储过程
CALL GetStudentCount(@count);
SELECT @count;
-- 创建带输入输出参数的存储过程
DELIMITER //
CREATE PROCEDURE UpdateStudentAge(INOUT studentAge INT)
BEGIN
SET studentAge = studentAge + 1;
END //
DELIMITER ;
-- 复杂存储过程示例
-- 根据条件查询学生并进行分页
DELIMITER //
CREATE PROCEDURE GetStudentsByPage(
IN minAge INT,
IN pageNum INT,
IN pageSize INT,
OUT totalRecords INT
)
BEGIN
-- 获取总记录数
SELECT COUNT(*) INTO totalRecords
FROM Students
WHERE Age >= minAge;
-- 获取分页数据
SELECT *
FROM Students
WHERE Age >= minAge
LIMIT pageSize OFFSET (pageNum - 1) * pageSize;
END //
DELIMITER ;
-- 删除存储过程
DROP PROCEDURE GetStudentsByAge;
-- 存储过程的优点:
-- 1. 提高代码复用性
-- 2. 减少网络流量,只传递参数
-- 3. 提高性能,预编译和缓存执行计划
-- 4. 增强安全性,限制数据访问
-- 5. 简化复杂业务逻辑
8.2 函数
函数是返回单个值的可重用代码块,可以在SQL语句中像内置函数一样使用。
sql
-- 创建标量函数
-- 计算学生的年级
DELIMITER //
CREATE FUNCTION GetGradeLevel(age INT) RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
DECLARE grade VARCHAR(20);
IF age < 18 THEN
SET grade = '未成年';
ELSEIF age < 22 THEN
SET grade = '大学低年级';
ELSE
SET grade = '大学高年级';
END IF;
RETURN grade;
END //
DELIMITER ;
-- 使用函数
SELECT Name, Age, GetGradeLevel(Age) as GradeLevel
FROM Students;
-- 创建聚合函数
-- 统计特定年龄段的学生数量
DELIMITER //
CREATE FUNCTION CountStudentsByAgeRange(minAge INT, maxAge INT)
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE count INT;
SELECT COUNT(*) INTO count
FROM Students
WHERE Age >= minAge AND Age <= maxAge;
RETURN count;
END //
DELIMITER ;
-- 使用函数
SELECT CountStudentsByAgeRange(18, 22) as StudentCount;
-- 删除函数
DROP FUNCTION GetGradeLevel;
-- 函数的特点:
-- 1. 必须返回单个值
-- 2. 可以在SQL语句中直接使用
-- 3. 比存储过程更简洁
-- 4. 适合进行计算和数据转换
-- 存储过程与函数的区别:
-- 1. 函数必须返回值,存储过程可以不返回
-- 2. 函数可以在SQL语句中调用,存储过程需要单独调用
-- 3. 函数通常用于计算,存储过程用于复杂操作
-- 4. 函数限制更多,不能执行某些操作
9. 事务
事务是数据库管理系统执行过程中的一个逻辑单位,由一系列操作组成。事务具有ACID特性,确保数据的完整性和一致性。
sql
-- 开始事务
-- 标志着事务的开始
START TRANSACTION;
-- 或
BEGIN;
-- 执行多个操作
-- 这些操作要么全部成功,要么全部失败
INSERT INTO Students VALUES (6, '钱八', 24, 'qianba@example.com');
UPDATE Students SET Age = 25 WHERE ID = 1;
DELETE FROM Students WHERE ID = 3;
-- 提交事务
-- 确认所有操作,将更改永久保存到数据库
COMMIT;
-- 回滚事务
-- 取消所有操作,恢复到事务开始前的状态
ROLLBACK;
-- 事务的ACID特性:
-- 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败
-- 一致性(Consistency):事务执行前后,数据库从一个一致性状态转换到另一个一致性状态
-- 隔离性(Isolation):并发执行的事务之间互不干扰
-- 持久性(Durability):事务提交后,其结果永久保存在数据库中
-- 事务隔离级别:
-- READ UNCOMMITTED:读未提交,可能读到未提交的数据(脏读)
-- READ COMMITTED:读已提交,避免脏读,但可能读到不可重复读的数据
-- REPEATABLE READ:可重复读,避免脏读和不可重复读,但可能读到幻读
-- SERIALIZABLE:串行化,最高隔离级别,避免所有并发问题
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 实际应用示例
-- 转账操作:从账户A转账100元到账户B
START TRANSACTION;
-- 检查账户A余额是否足够
SELECT Balance INTO @balanceA FROM Accounts WHERE ID = 1;
IF @balanceA >= 100 THEN
-- 扣除账户A的余额
UPDATE Accounts SET Balance = Balance - 100 WHERE ID = 1;
-- 增加账户B的余额
UPDATE Accounts SET Balance = Balance + 100 WHERE ID = 2;
-- 提交事务
COMMIT;
ELSE
-- 余额不足,回滚事务
ROLLBACK;
END IF;
-- 事务使用建议:
-- 1. 保持事务尽可能简短
-- 2. 避免在事务中进行用户交互
-- 3. 合理设置隔离级别
-- 4. 正确处理异常和错误
-- 5. 在关键操作中使用事务确保数据一致性
10. 实用技巧
10.1 常用SQL语句
这些语句在日常开发和维护中经常使用,可以帮助你快速获取数据库信息。
sql
-- 查看所有数据库
SHOW DATABASES;
-- 使用指定数据库
USE mydb;
-- 查看当前数据库
SELECT DATABASE();
-- 查看所有表
SHOW TABLES;
-- 查看表结构
DESCRIBE Students;
-- 或
SHOW COLUMNS FROM Students;
-- 查看创建表的SQL
-- 用于了解表的结构和约束
SHOW CREATE TABLE Students;
-- 查看表的索引
SHOW INDEX FROM Students;
-- 查看数据库连接
SHOW PROCESSLIST;
-- 查看数据库状态
SHOW STATUS;
-- 查看数据库变量
SHOW VARIABLES;
-- 查看表大小
SELECT
table_name,
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb
FROM information_schema.TABLES
WHERE table_schema = 'mydb';
-- 查看表的行数
SELECT table_name, table_rows
FROM information_schema.TABLES
WHERE table_schema = 'mydb';
10.2 性能优化建议
数据库性能优化是一个复杂的主题,以下是一些实用的优化技巧。
sql
-- 1. 为经常查询的列创建索引
CREATE INDEX idx_student_name ON Students(Name);
CREATE INDEX idx_student_age ON Students(Age);
-- 2. 避免使用 SELECT *,只查询需要的列
-- 不推荐
SELECT * FROM Students WHERE Age > 20;
-- 推荐
SELECT ID, Name, Age FROM Students WHERE Age > 20;
-- 3. 使用 LIMIT 限制返回结果数量
-- 特别是对于大数据量的查询
SELECT * FROM Students WHERE Age > 20 LIMIT 100;
-- 4. 合理使用 JOIN,避免过多表连接
-- 优化前:连接多个表
SELECT * FROM A
JOIN B ON A.id = B.id
JOIN C ON B.id = C.id
JOIN D ON C.id = D.id;
-- 优化后:考虑是否真的需要所有表
SELECT * FROM A
JOIN B ON A.id = B.id;
-- 5. 使用 EXISTS 代替 IN
-- IN 子查询可能性能较差
SELECT * FROM Students
WHERE ID IN (SELECT StudentID FROM Courses WHERE CourseName = '数学');
-- EXISTS 通常性能更好
SELECT * FROM Students s
WHERE EXISTS (SELECT 1 FROM Courses c WHERE c.StudentID = s.ID AND c.CourseName = '数学');
-- 6. 避免在 WHERE 子句中对列使用函数
-- 性能较差,无法使用索引
SELECT * FROM Students WHERE YEAR(CreateTime) = 2026;
-- 性能更好,可以使用索引
SELECT * FROM Students
WHERE CreateTime >= '2026-01-01' AND CreateTime < '2027-01-01';
-- 7. 使用批量操作代替循环操作
-- 不推荐:多次插入
INSERT INTO Students VALUES (1, '张三', 20);
INSERT INTO Students VALUES (2, '李四', 21);
INSERT INTO Students VALUES (3, '王五', 22);
-- 推荐:批量插入
INSERT INTO Students VALUES
(1, '张三', 20),
(2, '李四', 21),
(3, '王五', 22);
-- 8. 定期清理无用数据
-- 删除不再需要的数据,减少表大小
DELETE FROM Students WHERE CreateTime < '2020-01-01';
-- 9. 使用 EXPLAIN 分析查询性能
EXPLAIN SELECT * FROM Students WHERE Age > 20;
-- 10. 合理设计表结构
-- 选择合适的数据类型
-- 避免过度规范化
-- 适当反规范化提高查询性能
10.3 安全性建议
数据库安全是开发中不可忽视的重要方面。
sql
-- 1. 使用参数化查询防止SQL注入
-- 不安全:直接拼接SQL
string sql = "SELECT * FROM Students WHERE Name = '" + userName + "'";
-- 安全:使用参数化查询
string sql = "SELECT * FROM Students WHERE Name = @userName";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@userName", userName);
}
-- 2. 使用最小权限原则
-- 为应用创建专门的数据库用户,只授予必要的权限
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE ON mydb.Students TO 'app_user'@'localhost';
-- 3. 加密敏感数据
-- 对于密码等敏感信息,应该加密存储
INSERT INTO Users (Username, PasswordHash)
VALUES ('user1', HASH('sha256', 'password123'));
-- 4. 定期备份数据
-- 设置自动备份计划
mysqldump -u root -p mydb > backup_20260311.sql
-- 5. 限制远程访问
-- 只允许来自特定IP的连接
GRANT ALL PRIVILEGES ON mydb.* TO 'user'@'192.168.1.%';
-- 6. 使用SSL/TLS加密连接
-- 在生产环境中使用加密连接
mysql --ssl-mode=REQUIRED -u user -p
-- 7. 定期更新数据库软件
-- 及时应用安全补丁
-- 8. 审计数据库访问
-- 记录重要的数据库操作
11. C#中使用SQL
作为C#开发者,你需要掌握在C#应用程序中使用SQL的技术。
11.1 使用ADO.NET
ADO.NET是.NET框架提供的数据访问技术,提供了直接操作数据库的能力。
csharp
using System;
using System.Data.SqlClient;
class Program
{
static void Main()
{
// 数据库连接字符串
string connectionString = "Server=localhost;Database=mydb;User Id=sa;Password=yourpassword;";
// 查询数据
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
string sql = "SELECT * FROM Students WHERE Age > @age";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@age", 18);
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"ID: {reader["ID"]}, Name: {reader["Name"]}, Age: {reader["Age"]}");
}
}
}
}
// 插入数据
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
string sql = "INSERT INTO Students (ID, Name, Age, Email) VALUES (@id, @name, @age, @email)";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@id", 10);
cmd.Parameters.AddWithValue("@name", "新学生");
cmd.Parameters.AddWithValue("@age", 20);
cmd.Parameters.AddWithValue("@email", "newstudent@example.com");
int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"插入了 {rowsAffected} 行数据");
}
}
// 更新数据
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
string sql = "UPDATE Students SET Age = @age WHERE ID = @id";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@age", 21);
cmd.Parameters.AddWithValue("@id", 10);
int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"更新了 {rowsAffected} 行数据");
}
}
// 删除数据
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
string sql = "DELETE FROM Students WHERE ID = @id";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@id", 10);
int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"删除了 {rowsAffected} 行数据");
}
}
// 使用事务
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlTransaction transaction = conn.BeginTransaction();
try
{
// 执行多个操作
using (SqlCommand cmd1 = new SqlCommand("INSERT INTO Students VALUES (11, '学生A', 20)", conn, transaction))
{
cmd1.ExecuteNonQuery();
}
using (SqlCommand cmd2 = new SqlCommand("UPDATE Students SET Age = 21 WHERE ID = 1", conn, transaction))
{
cmd2.ExecuteNonQuery();
}
// 提交事务
transaction.Commit();
Console.WriteLine("事务执行成功");
}
catch (Exception ex)
{
// 回滚事务
transaction.Rollback();
Console.WriteLine($"事务失败:{ex.Message}");
}
}
// 调用存储过程
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("GetStudentsByAge", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@minAge", 20);
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"ID: {reader["ID"]}, Name: {reader["Name"]}");
}
}
}
}
}
}
11.2 使用Entity Framework
Entity Framework是微软提供的ORM(对象关系映射)框架,允许你使用.NET对象来操作数据库。
csharp
using System;
using System.Linq;
// 1. 定义实体类
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public DateTime CreateTime { get; set; }
}
// 2. 定义DbContext
public class MyDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public Microsoft.EntityFrameworkCore.DbSet<Student> Students { get; set; }
protected override void OnConfiguring(Microsoft.EntityFrameworkCore.DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=mydb;User Id=sa;Password=yourpassword;");
}
}
class Program
{
static void Main()
{
using (var context = new MyDbContext())
{
// 查询数据
var students = context.Students
.Where(s => s.Age > 18)
.OrderBy(s => s.Name)
.ToList();
foreach (var student in students)
{
Console.WriteLine($"ID: {student.ID}, Name: {student.Name}, Age: {student.Age}");
}
// 查询单个记录
var student = context.Students.Find(1);
if (student != null)
{
Console.WriteLine($"找到学生:{student.Name}");
}
// 添加新记录
var newStudent = new Student
{
ID = 20,
Name = "新学生",
Age = 20,
Email = "newstudent@example.com",
CreateTime = DateTime.Now
};
context.Students.Add(newStudent);
context.SaveChanges();
Console.WriteLine("添加了新学生");
// 更新记录
var updateStudent = context.Students.Find(20);
if (updateStudent != null)
{
updateStudent.Age = 21;
context.SaveChanges();
Console.WriteLine("更新了学生信息");
}
// 删除记录
var deleteStudent = context.Students.Find(20);
if (deleteStudent != null)
{
context.Students.Remove(deleteStudent);
context.SaveChanges();
Console.WriteLine("删除了学生");
}
// 使用LINQ进行复杂查询
var results = from s in context.Students
where s.Age >= 18 && s.Age <= 22
orderby s.Name
select new { s.Name, s.Age, s.Email };
foreach (var result in results)
{
Console.WriteLine($"{result.Name}, {result.Age}, {result.Email}");
}
// 使用Include加载关联数据
var studentsWithCourses = context.Students
.Include(s => s.Courses)
.ToList();
// 使用事务
using (var transaction = context.Database.BeginTransaction())
{
try
{
var student1 = new Student { ID = 21, Name = "学生1", Age = 20 };
var student2 = new Student { ID = 22, Name = "学生2", Age = 21 };
context.Students.Add(student1);
context.Students.Add(student2);
context.SaveChanges();
transaction.Commit();
Console.WriteLine("事务执行成功");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"事务失败:{ex.Message}");
}
}
// 使用原生SQL查询
var studentsFromSql = context.Students
.FromSqlRaw("SELECT * FROM Students WHERE Age > {0}", 18)
.ToList();
// 执行原生SQL命令
context.Database.ExecuteSqlRaw("UPDATE Students SET Age = Age + 1 WHERE ID = {0}", 1);
}
}
}
11.3 Dapper
Dapper是一个轻量级的ORM框架,性能接近ADO.NET,使用简单。
csharp
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using Dapper;
class Program
{
static void Main()
{
string connectionString = "Server=localhost;Database=mydb;User Id=sa;Password=yourpassword;";
using (var conn = new SqlConnection(connectionString))
{
// 查询数据
var students = conn.Query<Student>("SELECT * FROM Students WHERE Age > @Age", new { Age = 18 });
foreach (var student in students)
{
Console.WriteLine($"ID: {student.ID}, Name: {student.Name}");
}
// 查询单个记录
var student = conn.QuerySingleOrDefault<Student>("SELECT * FROM Students WHERE ID = @ID", new { ID = 1 });
// 插入数据
var newStudent = new Student { ID = 30, Name = "新学生", Age = 20 };
var rowsAffected = conn.Execute("INSERT INTO Students (ID, Name, Age) VALUES (@ID, @Name, @Age)", newStudent);
Console.WriteLine($"插入了 {rowsAffected} 行");
// 更新数据
rowsAffected = conn.Execute("UPDATE Students SET Age = @Age WHERE ID = @ID", new { Age = 21, ID = 30 });
Console.WriteLine($"更新了 {rowsAffected} 行");
// 删除数据
rowsAffected = conn.Execute("DELETE FROM Students WHERE ID = @ID", new { ID = 30 });
Console.WriteLine($"删除了 {rowsAffected} 行");
// 执行存储过程
var results = conn.Query<Student>("GetStudentsByAge", new { minAge = 20 }, commandType: System.Data.CommandType.StoredProcedure);
}
}
}
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
12. 学习建议
掌握SQL需要系统的学习和大量的实践。以下是一些学习建议:
-
基础优先: 先掌握基本的CRUD(创建、读取、更新、删除)操作,这是SQL的核心。理解SELECT、INSERT、UPDATE、DELETE的基本语法和用法
-
多练习: 通过实际项目练习SQL编写。可以设计一些练习场景,如学生管理系统、订单管理系统等,从简单到复杂逐步练习
-
理解原理: 深入了解索引、事务等底层原理。理解这些概念有助于写出更高效的SQL语句
-
学习优化: 学习SQL性能优化技巧。掌握EXPLAIN命令的使用,了解如何分析和优化查询性能
-
结合实际: 在C#项目中应用SQL知识。通过实际项目加深对SQL的理解和应用
-
循序渐进: 不要试图一次性掌握所有内容。从基本查询开始,逐步学习连接、子查询、存储过程等高级特性
-
多做实验: 在测试数据库中尝试各种SQL语句,观察结果,理解每个命令的作用
-
阅读源码: 学习优秀的开源项目中的SQL编写方式,学习最佳实践
-
关注安全: 始终关注SQL注入等安全问题,学习如何编写安全的SQL语句
-
持续学习: SQL技术不断发展,持续学习新的特性和最佳实践
13. 常见数据库
不同的数据库系统各有特点,了解它们有助于选择合适的数据库:
-
MySQL: 最流行的开源数据库,广泛用于Web应用。优点是免费、社区活跃、文档丰富;适合中小型项目
-
SQL Server: Microsoft开发的数据库,与C#和.NET框架集成良好。优点是强大的工具支持、良好的性能、企业级功能;适合Windows环境下的项目
-
PostgreSQL: 功能强大的开源数据库,支持复杂的数据类型和高级功能。优点是开源、功能丰富、扩展性强;适合需要复杂查询和高级功能的项目
-
SQLite: 轻量级的嵌入式数据库,无需服务器。优点是零配置、单文件、跨平台;适合移动应用、桌面应用和小型网站
-
Oracle: 企业级数据库系统,功能强大但成本较高。优点是稳定可靠、功能全面、安全性高;适合大型企业级应用
选择数据库时考虑因素:
- 项目规模和需求
- 预算限制
- 团队技术栈
- 社区支持和文档
- 性能要求
- 可扩展性需求
14. 学习资源
丰富的学习资源可以帮助你更快地掌握SQL:
在线教程和文档:
- W3Schools SQL教程:适合初学者的入门教程
- 菜鸟教程SQL:中文教程,内容全面
- 各数据库官方文档:最权威的参考资料
练习平台:
- LeetCode SQL练习:通过算法题练习SQL
- SQL Fiddle:在线SQL练习环境
- HackerRank SQL挑战:有趣的SQL练习题目
书籍推荐:
- 《SQL必知必会》:入门经典书籍
- 《高性能MySQL》:深入理解MySQL
- 《SQL反模式》:学习如何避免常见错误
实战项目:
- 设计并实现一个简单的学生管理系统
- 创建一个电商订单系统
- 开发一个博客系统
社区和论坛:
- Stack Overflow:解决具体问题
- 数据库相关论坛:交流学习经验
- GitHub开源项目:学习优秀的SQL实践
重要提示:
-
防止SQL注入: 在实际开发中,建议使用参数化查询来防止SQL注入攻击。始终对用户输入进行验证和清理
-
备份重要数据: 在执行任何可能修改或删除数据的操作前,务必备份重要数据
-
测试环境: 在生产环境执行任何SQL操作前,先在测试环境中验证
-
持续学习: SQL技术不断发展,保持学习新特性和最佳实践
-
安全第一: 始终考虑数据安全性,遵循最小权限原则
SQL是每个开发者都应该掌握的重要技能。通过系统学习和持续练习,你将能够高效地使用SQL来管理和操作数据,为你的应用程序提供强大的数据支持。祝你学习顺利!