DQL语法结构
DQL主包在上篇已经说名了他的定义和用处,现在来讲一下语法结构,让大家根据具体的理解和使用它,因为这个是我们日常开发中用的最多的一种Sql。
SELECT
[DISTINCT | ALL] column1,
column2,
... | *
FROM
table_name
[JOIN table2 ON condition]
[WHERE
condition]
[GROUP BY
column_name | expression]
[HAVING
condition]
[ORDER BY
column_name [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[FOR UPDATE | LOCK IN SHARE MODE]; -- (在特定事务隔离级别下使用,非DQL常用部分)
其实大家都知道DQL 的核心是 SELECT
语句,大概的结构大家其实也都明白,所以主包就只挑一些用的不多的关键词来讲了。
[DISTINCT | ALL]
这个不知道大家有没有用过,是给数据去重的,Mysql默认是ALL也就是读取全部的数据,而DISTINCT会对重复的数据进行去重。使用 DISTINCT
通常会导致数据库进行额外的排序和去重工作,可能会比 ALL
查询稍慢一些,尤其是在处理大量数据时。
condition
condition
是一个逻辑表达式,它定义了记录必须满足的规则,其最终结果为 TRUE
, FALSE
, 或 UNKNOWN
(通常由 NULL
值引起)。只有使条件结果为 TRUE
的记录才会被包含在结果集中,condition
可以由以下元素组成:
1.操作数 (Operands):
- 列名:
age
,name
,salary
- 常量/字面量:
100
,'John'
,'2023-01-01'
- 表达式:
salary * 1.1
,YEAR(order_date)
- 子查询 (Subquery):
(SELECT AVG(salary) FROM employees)
2.比较运算符 (Comparison Operators):用于比较两个值。
=
:等于<>
或!=
:不等于<
:小于>
:大于<=
:小于等于>=
:大于等于LIKE
:字符串模式匹配 (_
匹配单个字符,%
匹配任意字符)IN (...)
:判断是否在某个集合中BETWEEN ... AND ...
:判断是否在某个范围内IS NULL
/IS NOT NULL
:判断是否为NULL
3.逻辑运算符 (Logical Operators):用于连接多个条件。
AND
:逻辑与(两个条件都必须为真)OR
:逻辑或(至少一个条件为真)NOT
:逻辑非(取反)
这个就是我们用的比较多的条件了,用来筛选的,想必大家也不陌生啦。
expression
这个其实和上面的condition有点类似,他们的关系也是很微妙的。所有的 condition
都是 expression
,但并非所有的 expression
都是 condition
。他们的本质都是表达式,而expression产生一个值。这个值可以是一个数字、字符串、日期,或者甚至是布尔值(TRUE/FALSE)。而Condition是用来进行逻辑过滤。它本身就是一个特殊的表达式,但其结果总是被解释为布尔值(TRUE/FALSE),用于决定是否保留某行数据。
特性 |
Expression (表达式) |
Condition (条件) |
---|---|---|
目的 |
计算出一个值 |
判断真假,用于过滤 |
结果 |
任何数据类型(数字、字符串等) |
布尔值(TRUE/FALSE/UNKNOWN) |
主要使用场景 |
|
|
关系 |
条件是由表达式构成的 |
条件是表达式的子集 |
-- 这些都是表达式,它们最终都会计算出一个值
SELECT
salary, -- 列名表达式
salary * 1.1, -- 算术表达式
UPPER(last_name), -- 函数表达式
'Employee: ' || first_name, -- 字符串连接表达式
CASE WHEN salary > 50000 THEN 'High' ELSE 'Low' END -- 条件表达式
FROM employees;
-- 这些都是条件,它们用于过滤行
SELECT *
FROM employees
WHERE
salary > 50000 -- 比较条件
AND department_id = 10 -- 逻辑AND连接的条件
AND last_name LIKE 'S%'; -- 模式匹配条件
{[offset,] row_count | row_count OFFSET offset}
这个其实就是limit的两种写法效果是一样的。row_count
: 要返回的最大记录数,offset
: 要跳过的起始记录数(从0开始计数)。假设我们想获取第6到第15条记录(每页10条,即第2页)。
#写法1:LIMIT offset, row_count
SELECT * FROM products
ORDER BY product_name
LIMIT 5, 10; -- 跳过前5条,取接下来的10条
#写法2:LIMIT row_count OFFSET offset(更符合英语语法,推荐)
SELECT * FROM products
ORDER BY product_name
LIMIT 10 OFFSET 5; -- 取10条,从第5条之后开始(即第6条)
[FOR UPDATE | LOCK IN SHARE MODE](排他锁/共享锁)
这两个子句用于在事务中锁定被选中的行,防止其他会话修改,用于处理高并发下的数据一致性问题。必须在事务内使用(以 BEGIN
或 START TRANSACTION
开始)。
FOR UPDATE
(排他锁 / 写锁/X锁)
含义: 对查询结果集加排他锁。其他会话可以读取这些被锁定的行,但不能修改,也不能对这些行加 FOR UPDATE
或 LOCK IN SHARE MODE
锁。
场景: 当你查询数据后立即要更新它(如库存扣减、余额变更),需要确保在查询和更新之间数据不被别人修改。
在这个事务结束前,其他会话执行 SELECT ... FOR UPDATE
或 UPDATE
语句操作同一行时会被阻塞。
LOCK IN SHARE MODE
(共享锁 / 读锁/S锁)
含义: 对查询结果集加共享锁。其他会话可以读取这些行,也可以再加共享锁,但不能修改这些行,也不能加排他锁 (FOR UPDATE
)。
场景: 当你需要确保在你读取数据后、进行后续业务逻辑期间,这些数据不被别人修改(但允许别人读取),通常用于确保数据在事务期间的一致性读取。
特性 |
|
|
---|---|---|
核心思想 |
独占访问(读/写) |
共享读取,禁止写入 |
其他会话读取 |
✅ 可以(快照读) |
✅ 可以(当前读,也可加共享锁) |
其他会话修改 |
❌ 阻塞 |
❌ 阻塞 |
其他会话加共享锁 |
❌ 阻塞 |
✅ 可以(关键区别) |
其他会话加排他锁 |
❌ 阻塞 |
❌ 阻塞 |
并发性能 |
低(串行化访问) |
高(允许多个读者协同) |
典型场景 |
我要修改,别碰它 |
我们一起看清楚,然后再决定谁能改 |
首先我们使用这两种锁的时候是必须显式声明事务的,这是因为锁的持有需要有一个明确的作用域和生命周期,而事务正是定义这个生命周期的作用域。锁需要在事务开始时获取,并在事务结束时(提交或回滚)释放。
然后再来看这两个的读取,一个是快照读一个是当前读,有什么区别呢?在开启事务(START TRANSACTION;
)后,MySQL 就会生成一个“一致性视图”或“快照”,这个快照不是物理拷贝所有数据,而是一个逻辑概念,它记录了事务开始时整个数据库的逻辑状态(主要是通过事务ID和undo log来实现)。这个是什么意思呢?简单来说就是修改的这一条数据以及事务ID会被存到undoLOG中,然后在新数据行上记录一个指针,指向它在Undo Log中的旧版本。所以这个快照读其实就是读的上一个版本的信息,读取不到事务中已经修改但是没提交的信息。而当前读就是实时的新数据。
然后就是这个共享锁的禁止写入是什么意思呢?是这样的如果一行数据被加了共享锁,然后要修改这个数据的时候会被强制升级为排他锁,而且前提下是不能有其他的共享锁或者排他锁,不然就会死锁或阻塞等待。也就是如果A和B事务同时加锁且等待不释放锁的话,就会死锁。
时间 |
会话 A |
会话 B |
数据库状态与说明 |
---|---|---|---|
t1 |
|
|
|
t2 |
|
|
|
t3 |
|
会话A的UPDATE被阻塞! |
|
t4 |
(阻塞中...) |
|
会话B的UPDATE也被阻塞! |
t5 |
数据库的死锁检测机制(InnoDB)会在几秒后介入。它会选择其中一个会话(通常是代价最小的那个,如B)作为“受害者”,回滚其事务并释放其持有的所有锁(包括S锁)。 |
||
t6 |
(会话B的S锁被释放) |
|
行锁状态:S-lock by A |
t7 |
|
事务结束,锁全部释放。 |
大概的流程就是这样。大家细细品尝吧内容有点多了。
执行流程
助记口诀:从哪张表拿数据(FROM/JOIN) -> 筛选行(WHERE) -> 分组(GROUP BY) -> 筛选组(HAVING) -> 选择列(SELECT) -> 去重(DISTINCT) -> 排序(ORDER BY) -> 限制条数(LIMIT)
总结
这篇的内容有点多啦,还有些没相关的内容没讲完,留在下期说吧,然后本篇主要将啦DQL的语法结构和执行的流程。