Mysql杂志(二)

发布于:2025-08-29 ⋅ 阅读:(18) ⋅ 点赞:(0)

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列表, GROUP BY, ORDER BY

WHERE, HAVING, JOIN...ON

关系

条件是由表达式构成的

条件是表达式的子集

-- 这些都是表达式,它们最终都会计算出一个值
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](排他锁/共享锁)

这两个子句用于在事务中锁定被选中的行,防止其他会话修改,用于处理高并发下的数据一致性问题。​必须在事务内使用(以 BEGINSTART TRANSACTION开始)​

FOR UPDATE(排他锁 / 写锁/X锁)​

含义: 对查询结果集加排他锁。其他会话可以读取这些被锁定的行,但不能修改,也不能对这些行加 FOR UPDATELOCK IN SHARE MODE锁。

场景: 当你查询数据后立即要更新它(如库存扣减、余额变更),需要确保在查询和更新之间数据不被别人修改。

在这个事务结束前,其他会话执行 SELECT ... FOR UPDATEUPDATE语句操作同一行时会被阻塞。

LOCK IN SHARE MODE(共享锁 / 读锁/S锁)​

含义: 对查询结果集加共享锁。其他会话可以读取这些行,也可以再加共享锁,但不能修改这些行,也不能加排他锁 (FOR UPDATE)。

场景: 当你需要确保在你读取数据后、进行后续业务逻辑期间,这些数据不被别人修改​(但允许别人读取),通常用于确保数据在事务期间的一致性读取。

特性

FOR UPDATE(排他锁)

LOCK IN SHARE MODE(共享锁)

核心思想

独占访问(读/写)

共享读取,​禁止写入

其他会话读取

✅ 可以(快照读)

✅ 可以(当前读,也可加共享锁)

其他会话修改

❌ 阻塞

❌ 阻塞

其他会话加共享锁

❌ 阻塞

✅ 可以(关键区别

其他会话加排他锁

❌ 阻塞

❌ 阻塞

并发性能

低(串行化访问)

高(允许多个读者协同)​

典型场景

我要修改,别碰它

我们一起看清楚,然后再决定谁能改

首先我们使用这两种锁的时候是必须显式声明事务的,这是因为锁的持有需要有一个明确的作用域和生命周期,而事务正是定义这个生命周期的作用域。锁需要在事务开始时获取,并在事务结束时(提交或回滚)释放。

然后再来看这两个的读取,一个是快照读一个是当前读,有什么区别呢?​在开启事务(START TRANSACTION;)后,MySQL 就会生成一个“一致性视图”或“快照”,这个快照不是物理拷贝所有数据,而是一个逻辑概念,它记录了事务开始时整个数据库的逻辑状态​(主要是通过事务ID和undo log来实现)。这个是什么意思呢?简单来说就是修改的这一条数据以及事务ID会被存到undoLOG中,然后在新数据行上记录一个指针,指向它在Undo Log中的旧版本。所以这个快照读其实就是读的上一个版本的信息,读取不到事务中已经修改但是没提交的信息。而当前读就是实时的新数据。

然后就是这个共享锁的禁止写入是什么意思呢?是这样的如果一行数据被加了共享锁,然后要修改这个数据的时候会被强制升级为排他锁,而且前提下是不能有其他的共享锁或者排他锁,不然就会死锁或阻塞等待。也就是如果A和B事务同时加锁且等待不释放锁的话,就会死锁。

时间

会话 A

会话 B

数据库状态与说明

t1

START TRANSACTION;
SELECT * FROM products WHERE id=1 LOCK IN SHARE MODE;
​// 获得 S锁

stock = 100
行锁状态:​S-lock by A

t2

START TRANSACTION;
SELECT * FROM products WHERE id=1 LOCK IN SHARE MODE;
​// 获得 S锁

stock = 100
行锁状态:​S-lock by A, S-lock by B

t3

UPDATE products SET stock = 90 WHERE id=1;
​// 尝试获取 X锁

会话A的UPDATE被阻塞!​
因为它无法获取X锁(该行上还有B持有的S锁)。
会话A进入锁等待状态。

t4

​(阻塞中...)​

UPDATE products SET stock = 80 WHERE id=1;
​// 尝试获取 X锁

会话B的UPDATE也被阻塞!​
它也无法获取X锁(该行上还有A持有的S锁)。
此时发生死锁(Deadlock)或锁等待(Lock Wait)!​

t5

数据库的死锁检测机制(InnoDB)会在几秒后介入。它会选择其中一个会话(通常是代价最小的那个,如B)作为“受害者”,​回滚其事务并释放其持有的所有锁(包括S锁)。

t6

​(会话B的S锁被释放)​

ERROR 1213 (40001): Deadlock found
​// 事务被强制回滚

行锁状态:​S-lock by A
会话A的UPDATE操作因为B的锁已释放,​成功获取X锁

t7

UPDATE成功执行!
stock变为 90
COMMIT;​// 释放X锁

事务结束,锁全部释放。
stock = 90

大概的流程就是这样。大家细细品尝吧内容有点多了。

执行流程

助记口诀:从哪张表拿数据(FROM/JOIN) -> 筛选行(WHERE) -> 分组(GROUP BY) -> 筛选组(HAVING) -> 选择列(SELECT) -> 去重(DISTINCT) -> 排序(ORDER BY) -> 限制条数(LIMIT)​

总结

这篇的内容有点多啦,还有些没相关的内容没讲完,留在下期说吧,然后本篇主要将啦DQL的语法结构和执行的流程。


网站公告

今日签到

点亮在社区的每一天
去签到