MySQL | SQL

本文更新于 2026-03-28

基础书写规则

  • 分号结束:每条 SQL 语句通常以分号 ; 结尾(虽然某些单条执行环境不强制,但在脚本中是必需的)。

  • 不区分大小写:SQL 关键字(如 SELECT, FROM)不区分大小写,但为了代码可读性,习惯上关键字大写,表名和列名小写。

    • 推荐写法: SELECT name FROM users;
  • 空格与缩进:SQL 对空格和换行不敏感。合理的缩进能显著提高代码的可读性。

  • 注释规范

    • 单行注释:-- 注释内容# 注释内容(仅限 MySQL)。

    • 多行注释:/* 注释内容 */

缩写全称操作对象常用动词场景示例
DDLDefinition表结构/数据库CREATE, DROP“建个名为‘用户’的表”
DMLManipulation行记录INSERT, UPDATE“把小明的年龄改成18”
DQLQuery数据结果集SELECT“找出所有北京的用户”
DCLControl权限/安全GRANT, REVOKE“允许小李查看工资表”

数据类型

整型

类型名称取值范围大小
TINYINT-128〜1271个字节
SMALLINT-32768〜327672个宇节
MEDIUMINT-8388608〜83886073个字节
INT (INTEGER)-2147483648〜21474836474个字节
BIGINT-9223372036854775808〜92233720368547758078个字节

无符号在数据类型后加 unsigned 关键字。

浮点型

类型名称说明存储需求
FLOAT单精度浮点数4 个字节
DOUBLE双精度浮点数8 个字节
DECIMAL (M, D),DEC压缩的“严格”定点数M+2 个字节

日期和时间

类型名称日期格式日期范围存储需求
YEARYYYY1901 ~ 21551 个字节
TIMEHH:MM:SS-838:59:59 ~ 838:59:593 个字节
DATEYYYY-MM-DD1000-01-01 ~ 9999-12-33 个字节
DATETIMEYYYY-MM-DD HH:MM:SS1000-01-01 00:00:00 ~ 9999-12-31 23:59:598 个字节
TIMESTAMPYYYY-MM-DD HH:MM:SS1980-01-01 00:00:01 UTC ~ 2040-01-19 03:14:07 UTC4 个字节

字符串

类型名称说明存储需求
CHAR(M)固定长度非二进制字符串M 字节,1<=M<=255
VARCHAR(M)变长非二进制字符串L+1字节,在此,L <= M和 1<=M<=255
TINYTEXT非常小的非二进制字符串L+1字节,在此,L<2^8
TEXT小的非二进制字符串L+2字节,在此,L<2^16
MEDIUMTEXT中等大小的非二进制字符串L+3字节,在此,L<2^24
LONGTEXT大的非二进制字符串L+4字节,在此,L<2^32
ENUM枚举类型,只能有一个枚举字符串值1或2个字节,取决于枚举值的数目 (最大值为65535)
SET一个设置,字符串对象可以有零个或 多个SET成员1、2、3、4或8个字节,取决于集合 成员的数量(最多64个成员)

二进制类型

类型名称说明存储需求
BIT(M)位字段类型大约 (M+7)/8 字节
BINARY(M)固定长度二进制字符串M 字节
VARBINARY (M)可变长度二进制字符串M+1 字节
TINYBLOB (M)非常小的BLOBL+1 字节,在此,L<2^8
BLOB (M)BLOBL+2 字节,在此,L<2^16
MEDIUMBLOB (M)中等大小的BLOBL+3 字节,在此,L<2^24
LONGBLOB (M)非常大的BLOBL+4 字节,在此,L<2^32

DDL

CREATE(创建)

用于建立新的数据库对象。

-- 创建一个名为 users 的表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自动增长
    username VARCHAR(50) NOT NULL,      -- 不允许为空
    email VARCHAR(100) UNIQUE,          -- 唯一约束
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 默认值
);

ALTER(修改)

用于修改现有的表结构,例如增加新列、更改数据类型或重命名。

  • 1.添加字段

    ALTER TABLE 表名 ADD 字段名 类型(长度) [comment] [约束];

  • 2.修改数据类型

    ALTER TABLE MODIFY 字段名 新数据类型(长度);

  • 3.修改字段名和字段类型

    ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度)[comment][约束];

  • 4.删除字段

    ALTER TABLE 表名 DROP 字段名;

  • 5.修改表名

    ALTER TABLE 表名 RENAME TO 新表名

-- 在 users 表中增加一行“手机号”
ALTER TABLE users ADD phone_number VARCHAR(20);

-- 修改字段类型
ALTER TABLE users MODIFY COLUMN username VARCHAR(100);

-- 删除某一列
ALTER TABLE users DROP COLUMN email;

DROP(删除)

将整个对象从数据库中彻底抹去。

-- 删除整个 users 表(结构和数据全部消失)
DROP TABLE users;

-- 删除整个数据库
DROP DATABASE my_app;

TRUNCATE(清空)

用于删除表中的所有数据,但保留表结构。

  • 与 DELETE 的区别DELETE 是一行行删,速度慢;TRUNCATE 是直接销毁表并重建一个空的,速度极快且不记录逐行日志。
TRUNCATE TABLE users;

DML

  • DML 主要由三个核心关键字组成:INSERT(增)、UPDATE(改)、DELETE(删)。

INSERT (插入数据)

用于向表中添加新行。

  • 指定列插入:(推荐,更安全)
	INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (1, 2, ...);
	
	INSERT INTO users (username, age, email) 
	VALUES ('张三', 25, 'zhangsan@example.com');
  • 全列插入:(必须按表结构顺序提供所有值)
	INSERT INTO 表名 VALUES (1, 2, ...);

	INSERT INTO users VALUES (NULL, '李四', 30, 'lisi@example.com', NOW());
  • 批量插入:
    -- 指定字段的批量插入(推荐)
	INSERT INTO 表名 (字段名1, 字段名2, ...) 
	VALUES 
		(1, 2, ...),
		(1, 2, ...),
		(1, 2, ...);
	
	INSERT INTO student (name, age, gender) 
	VALUES 
		('张三', 20, '男'),
		('李四', 19, '女'), 
		('王五', 21, '男');
	
	-- 省略字段的批量插入(所有字段)
	INSERT INTO 表名 
	VALUES 
		(1, 2, ...),
		(1, 2, ...),
		(1, 2, ...);
		
	INSERT INTO student
	VALUES
	    (4, '赵六', 20, '男'),
	    (5, '钱七', 18, '女');

[!IMPORTANT]

  • 添加数据时,字段名与值的顺序、个数必须一一对应
  • 字符串类型和日期类型的值,需要包含在引号中。
  • 插入的数据必须在字段允许的范围之内

UPDATE (更新数据)

用于修改表中已存在的记录。

  • 关键点: 永远不要忘记 WHERE 子句,否则全表数据都会被修改!
	
	UPDATE 表名 SET 字段名1 = 1, 字段名2 = 2, ... [WHERE 条件];

	UPDATE users SET age = 26, email = 'san_new@example.com' 
	WHERE username = '张三'; -- 只更新张三的数据

[!NOTE] 注意:修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据。

DELETE (删除数据)

用于删除表中的特定行。

  • 关键点: 同样必须配合 WHERE 使用,否则会清空整个表的数据。
	DELETE FROM 表名 [WHERE 条件];


    DELETE FROM users 
    WHERE id = 101; -- 删除 ID 为 1

[!NOTE]

  • DELETE 语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数据。
  • DELETE 语句不能删除某一个字段的值(可以使用 UPDATE)。

对比

维度DELETE (DML)TRUNCATE (DDL)
底层逻辑逐行扫描与标记。像“精细手术”,逐条检查并标记删除。数据页释放。像“拆迁重建”,直接解除数据页与表的映射。
事务日志 (Logging)全额记录。记录每一行数据的变化,以便回滚。最小化记录。仅记录页面释放的操作,日志开销极小。
执行速度。随着数据量增加,性能呈线性下降。极快。无论数据量是 1 万还是 1 亿,耗时基本一致。
空间回收 (HWM)不释放。高水位线(High Water Mark)不变,表占用的物理空间不减小。立即释放。重置高水位线,将空间还给表空间/操作系统。
自增列 (Identity)不重置。删除后插入新数据,ID 会接着原来的序号递增。重置。将自增计数器恢复到初始状态(通常是 1)。
触发器 (Triggers)激活。每一行被删除时都会触发对应的 DELETE 触发器。忽略。不会激活任何触发器,因为不进行逐行操作。
WHERE 子句支持。可以精确删除某一部分符合条件的数据。不支持。只能“一锅端”,清空整个表。
回滚能力 (Rollback)支持。在未提交的事务中可以完全撤销。视数据库而定。MySQL 等多数库不支持,PostgreSQL/SQL Server 支持。
外键约束支持。受外键约束限制,若有关联数据会报错。受限。如果表被外键引用,通常必须先删除外键才能执行。
锁机制行级锁。执行时只锁定被操作的行,并发性较高。表级锁。执行时通常会锁定整张表,阻止其他访问。

什么时候必须用 DELETE

  • 需要根据特定条件(如 status = 'expired')删除数据时。

  • 当你需要依赖 事务安全性(即如果操作失败,必须能撤回所有更改)时。

  • 表中有复杂的 触发器 逻辑需要维持数据的一致性或审计。

什么时候必须用 TRUNCATE

  • 清理日志表/临时表:当数据已经过期且量级巨大(百万、千万级以上)时。

  • 重置环境:在开发测试阶段,希望快速清空表并让自增主键归零。

  • 优化存储:当表删除大量数据后,物理空间依然占用过高,需要通过 TRUNCATE 彻底释放磁盘空间。

DQL

基本查询

  • 设置别名(别名会在表头显示)
	SELECT 字段1 [AS 别名1], 字段2 [AS 别名2] ... FROM 表名;

	-- 使用 AS 给列起中文名
	SELECT name, age AS '年龄' FROM users; 
	
	-- 直接空格加别名
	SELECT name '姓名', entrydate '入职日期' FROM emp;
  • **去除重复记录 **(DISTINCT)
	SELECT DISTINCT 字段列表 FROM 表名;
	
	-- 查询公司有哪些不同的工作岗位 单字段去重
	SELECT DISTINCT job FROM emp;
	
	-- 只有当 job 和 dept 同时相同时才会被去重 多字段去重
	SELECT DISTINCT job, dept FROM emp;

条件查询

语法

	SELECT 字段列表 FROM 表名 WHERE 条件列表;

条件

比较运算符

运算符功能描述示例代码块
>, >=, <, <=大于、大于等于、小于、小于等于WHERE age >= 18
=, <>!=等于、不等于WHERE job != '经理'
BETWEEN … AND …在范围内(含最小值和最大值)WHERE age BETWEEN 20 AND 30
IN(…)在指定的集合/列表中,多选一WHERE city IN ('北京', '上海', '广州')
LIKE 占位符模糊匹配_ 匹配单个字符,% 匹配任意字符)WHERE name LIKE '张%' (姓张的)
IS NULL判断值是否为空WHERE email IS NULL

逻辑运算符

运算符功能描述示例代码块
AND&&并且(多个条件必须同时成立)WHERE age > 18 AND gender = '女'
OR 或 ``
NOT!非、不是(取反)WHERE age NOT BETWEEN 20 AND 30

聚合函数

常见聚合函数

函数功能示例代码
COUNT()统计数量(行数)SELECT COUNT(*) FROM emp;
SUM()求和SELECT SUM(salary) FROM emp;
AVG()求平均值SELECT AVG(salary) FROM emp;
MAX()求最大值SELECT MAX(age) FROM emp;
MIN()求最小值SELECT MIN(age) FROM emp;

基础语法

聚合函数通常直接紧跟在 SELECT 之后:

	SELECT 聚合函数(字段列表) FROM 表名 [WHERE 条件];

聚合函数注意点:

NULL 值被忽略:所有的聚合函数(尤其是 COUNT, AVG, SUM)都会自动忽略 NULL 值

_例子_:如果 10 个人里有 2 个人没发工资(NULL),`AVG(salary)` 的结果是 `总工资 / 8`,而不是除以 10。

WHERE 的限制:聚合函数不能直接写在 WHERE 子句中。

_错误写法_:`SELECT * FROM emp WHERE salary > AVG(salary);` (这是初学者最容易犯的错)。

正确做法:需要使用子查询或使用 HAVING

单值输出:聚合函数的结果是一个单行单列的数值。除非配合 GROUP BY,否则不能在 SELECT 中同时查询聚合函数和普通字段(如 SELECT name, COUNT(*) 在大多数数据库中是逻辑错误的)。

分组查询

  • 基本查询
	SELECT 字段列表 FROM 表名 [WHERE 条件] GROUP BY 分组字段名 [HAVING 分组后过滤条件];
	
--	执行时机不同:where 是分组之前进行过滤,不满足 where 条件,不参与分组;而 having 是分组之后对结果进行过滤。
--	判断条件不同:where 不能对聚合函数进行判断,而 having 可以.
	WHERE > 聚合函数 > HAVING

注意点:

	-- 逻辑 1:统计行数(包含 NULL)
	-- 只要这一组有 2 行,哪怕地址全是空,结果也显示 2
	SELECT workaddress, COUNT(*) 
	FROM emp 
	GROUP BY workaddress 
	HAVING COUNT(*) >= 2;
	
	-- 逻辑 2:统计非空值数量
	-- 虽然这一组有 2 行(满足 HAVING),但由于这两行地址是 NULL,
	-- COUNT(workaddress) 统计不到任何非空值,结果显示 0
	SELECT workaddress, COUNT(workaddress) 
	FROM emp 
	GROUP BY workaddress 
	HAVING COUNT(*) >= 2;

排序查询

  • 基本查询
	SELECT 字段列表 FROM 表名 [WHERE 条件] [GROUP BY 分组字段] [HAVING 过滤条件] ORDER BY 字段1 排序方式1, 字段2 排序方式2;
	
	ASC: 升序(默认值)
	DESC:降序
	
	-- 需求:查询所有员工信息,并按照年龄升序排序
	SELECT * FROM emp ORDER BY age ASC;
	
	-- 需求:查询所有员工,按入职时间降序排序(最近入职的在前)
	SELECT * FROM emp ORDER BY entrydate DESC;
  • 多字段排序(二次排序)
	-- 需求:按照年龄升序排序;如果年龄相同,再按照入职时间降序排序
	SELECT * FROM emp ORDER BY age ASC, entrydate DESC;

[!NOTE] NULL:MySQL 中,NULL 被视为最小值 执行顺序ORDER BY 是在 SELECT 之后执行的。这意味着你可以在 ORDER BY 中使用你在 SELECT 里起的别名

分页查询

  • 基础语法
	SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数;
	-- 查询第一页 (前 10 条)
	-- 起始索引为 0 时,可以省略不写
	SELECT * FROM emp LIMIT 0, 10;
	-- 等价于:
	SELECT * FROM emp LIMIT 10;
	
	-- 查询第二页 (第 11-20 条)
	-- 从索引 10 开始(即第 11 条),往后查 10 条
	SELECT * FROM emp LIMIT 10, 10;
	
	-- 查询第三页 (第 21-30 条)
	SELECT * FROM emp LIMIT 20, 10;

计算公式: 起始索引 = (查询页码 - 1) * 每页显示记录数

  • 例子:想看第 5 页,每页 10 条。

  • 计算(5 - 1) * 10 = 40

  • SQLSELECT * FROM emp LIMIT 40, 10;

另外注意点:

  • 方言限制LIMITMySQL 的方言。如果用的是 Oracle,需要用 ROWNUM;如果是 SQL Server,需要用 TOP

  • 执行顺序LIMIT 永远是 SQL 语句中最后执行的一个环节

  • 大分页性能:当数据量达到百万级,LIMIT 1000000, 10 会变得很慢,因为数据库需要先扫描前 100 万条再丢弃,后续需要通过索引优化或子查询来解决

执行顺序 (逻辑顺序)

理解 DQL 的关键不在于怎么写,而在于数据库怎么读。书写顺序和执行顺序是不一样的:

	SELECT
	    字段列表          -- 5. 确定要显示的字段
	FROM
	    表名列表          -- 1. 确定去哪张表找
	WHERE
	    条件列表          -- 2. 第一次行过滤
	GROUP BY
	    分组字段列表      -- 3. 数据分组
	HAVING
	    分组后过滤条件    -- 4. 第二次组过滤
	ORDER BY
	    排序字段列表      -- 6. 对结果进行排序
	LIMIT
	    起始索引, 记录数;  -- 7. 截取最终结果
执行步骤关键字作用
Step 1FROM确定去哪张表找数据。表名列表
Step 2WHERE按照条件过滤原始行数据。条件列表
Step 3GROUP BY将过滤后的数据分组(如按部门)。分组字段列表
Step 4HAVING对分组后的结果再次进行过滤。分组后条件列表
Step 5SELECT选出最终要显示的列。字段列表
Step 6ORDER BY对最终结果进行排序。排序字段列表
Step 7LIMIT限制显示的行数(分页)。分页参数

DCL

管理用户

  • 查询用户
	USE mysql;
	SELECT * FROM user;
  • 创建用户
	CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; 
	
	
	-- 示例:创建一个只能在当前主机(localhost)登录的用户
	CREATE USER 'work_user'@'localhost' IDENTIFIED BY '123456';
	
	-- 示例:创建一个可以在任意主机(%)登录的用户
	CREATE USER 'remote_user'@'%' IDENTIFIED BY '123456';
  • 修改/删除用户
	-- 修改用户密码 (MySQL 8.0+)
	ALTER USER 'work_user'@'localhost' 
	IDENTIFIED WITH mysql_native_password BY 'new_password';
	
	-- 删除用户
	DROP USER 'work_user'@'localhost';

权限控制

  • 常用权限
权限名称作用描述
SELECT查询数据(最常用)
INSERT插入新数据
UPDATE修改现有数据
DELETE删除现有数据
ALTER修改表结构(如增加列)
DROP删除表或数据库
CREATE创建新表或数据库
ALL [PRIVILEGES]所有权限(最高权限)
  • 查询权限
	-- 查询指定用户的权限
	SHOW GRANTS FOR 'heima'@'localhost';
  • 授予权限
	GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
	
	-- 1. 授予单个权限:允许用户查询 test 库中的 emp 表
	GRANT SELECT ON test.emp TO 'heima'@'localhost';
	
	-- 2. 授予多个权限:允许用户对 test 库所有表进行查询、插入、修改
	GRANT SELECT, INSERT, UPDATE ON test.* TO 'heima'@'localhost';
	
	-- 3. 授予所有权限:允许用户操作所有数据库的所有表(DBA 级别)
	GRANT ALL ON *.* TO 'admin'@'%';
  • 撤销权限
	REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
	
	-- 撤回用户对 test 库所有表的插入(INSERT)权限
	REVOKE INSERT ON test.* FROM 'heima'@'localhost';
	
	-- 撤回所有权限
	REVOKE ALL ON *.* FROM 'admin'@'%';
今日访问 ... 次 | 今日访客 ... 人 | 本页阅读 ...
小站已萌萌哒运行了 0 0 0
已累计耕耘 33 篇博文 · 共 115.17k 个字
总访问量 ...
备案图标 豫公网安备41019702004633号