阅读视图

面试笔记:MySQL 相关03 – SQL语法与查询优化

感谢订阅陶其的个人博客!

SQL语法与查询优化

回目录: 《面试笔记:MySQL 相关目录》
上一篇: 《面试笔记:MySQL 相关02 – 索引》


01. 子查询与连接(join)查询

子查询:是嵌套在主查询中的查询,按“返回结果类型”和“是否依赖主查询”分类,核心是 “子结果驱动主查询”;
连接查询:是多表关联,核心是 “通过关联条件合并表数据”,按“是否保留不匹配行”分类,底层依赖驱动表和被驱动表的关联逻辑。

以下给出了4种核心子查询和5种核心连接查询,以及一种内部优化查询。

01. 标量子查询

返回单个值。

  • 写法格式:子查询返回 1 行 1 列,可作为主查询的字段或条件值;
    -- 示例:查询订单数最多的用户名称(子查询返回最大订单数)
    SELECT username FROM user WHERE user_id = (SELECT MAX(user_id) FROM order WHERE status = 1);
  • 性能开销:极低,仅执行 1 次子查询,结果直接代入主查询;
  • 适用场景:主查询需依赖单个聚合值(如最大值、平均值)或特定单行数据。

02. 列子查询

返回单列多行。

  • 写法格式:子查询返回 1 列 N 行,主查询用IN/NOT IN/ANY/ALL匹配;
    -- 示例:查询有未支付订单的用户(子查询返回未支付订单的用户ID列表)
    SELECT username FROM user WHERE user_id IN (SELECT DISTINCT user_id FROM order WHERE status = 0);
  • 性能开销:中低,MySQL 5.7 + 会将其优化为 “半连接”(避免逐行匹配),子查询结果会被物化(存入临时表);
  • 适用场景:主查询需匹配多个候选值(如 “在某个集合中”),子查询结果集不宜过大(建议 1 万行内)。

03. 行子查询

返回多行多列。

  • 写法格式:子查询返回 N 行 N 列(通常 1-2 列),主查询用IN匹配行数据;
    -- 示例:查询与“用户张三的上海地址”完全匹配的用户(返回id和city两列)
    SELECT username FROM user WHERE (user_id, city) IN (SELECT user_id, city FROM user_addr WHERE username = '张三' AND city = '上海');
  • 性能开销:中,子查询结果集需按行匹配,建议结果集控制在 1 千行内;
  • 适用场景:主查询需匹配 “多字段组合条件”(如联合主键、多维度筛选)。

04. 关联子查询

依赖主查询字段。

  • 写法格式:子查询用主查询的字段作为条件(EXISTS是典型用法),需用表别名区分;
    -- 示例:查询有有效订单的用户(子查询依赖主查询的user_id,找到匹配即停止)
    SELECT username FROM user u WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.user_id AND o.status = 1);
  • 性能开销:中高,主查询每扫描 1 行,子查询触发 1 次(类似 “嵌套循环”);若子查询关联字段无索引,会导致全表扫描(如 10 万行主表触发 10 万次子查询);
  • 适用场景:主查询需 “存在性校验”(如 “是否有相关数据”),优先用EXISTS(找到匹配行立即停止)替代IN(需遍历全量结果集)。

05. 内连接

INNER JOIN,默认连接方式。

  • 写法格式:仅保留两表中 “关联条件完全匹配” 的行(取交集),INNER可省略;
    -- 示例:查询用户及其有效订单(仅返回有订单的用户和匹配的订单)
    SELECT u.username, o.order_no FROM user u
    INNER JOIN order o ON u.user_id = o.user_id  -- 关联条件(必须写ON),INNER 可省略
    WHERE o.status = 1;
  • 性能开销:低,MySQL 会自动选择 “小表作为驱动表”,若关联字段有索引,被驱动表可快速匹配(避免全表扫描);
  • 适用场景:需获取多表 “交集数据”(如用户 + 关联订单、商品 + 关联分类),是业务中最常用的连接方式。

06. 左外连接

LEFT JOIN / LEFT OUTER JOIN

  • 写法格式:保留左表所有行,右表无匹配时用NULL填充,OUTER可省略;
    -- 示例:查询所有用户及其订单(无订单的用户也会显示,订单字段为NULL)
    SELECT u.username, o.order_no FROM user u
    LEFT JOIN order o ON u.user_id = o.user_id
    WHERE o.status = 1 OR o.order_no IS NULL;  -- 注意:过滤右表需包含NULL
  • 性能开销:中低,驱动表是左表(需全扫左表),右表关联字段有索引则性能接近内连接;无索引则性能下降明显;
  • 适用场景:需保留主表全量数据(如用户表),关联从表(如订单表)的可选数据(如 “统计所有用户的订单情况,包括无订单用户”)。

07. 右外连接

RIGHT JOIN / RIGHT OUTER JOIN

  • 写法格式:保留右表所有行,左表无匹配时用NULL填充,逻辑与左连接相反;
    -- 示例:查询所有订单及其所属用户(无匹配用户的订单也显示,用户字段为NULL)
    SELECT u.username, o.order_no FROM user u
    RIGHT JOIN order o ON u.user_id = o.user_id
    WHERE o.create_time > '2024-01-01';
  • 性能开销:与左连接一致,驱动表是右表(需全扫右表);
  • 适用场景:需保留从表全量数据(如订单表),关联主表(如用户表)的可选数据(如 “统计所有近 3 个月订单,包括已删除用户的订单”)。

08. 全外连接

FULL JOIN / FULL OUTER JOIN

  • 写法格式:保留左右两表所有行,无匹配时用NULL填充;MySQL 不直接支持FULL JOIN,需用UNION合并左连接和右连接;
    -- 示例:查询所有用户和所有订单的关联数据(保留双方无匹配的行)
    SELECT u.username, o.order_no FROM user u
    LEFT JOIN order o ON u.user_id = o.user_id
    UNION  -- 去重合并
    SELECT u.username, o.order_no FROM user u
    RIGHT JOIN order o ON u.user_id = o.user_id;
  • 性能开销:高,需执行两次连接 + 去重操作,数据量大时效率低;
  • 适用场景:极少用,仅需 “完整保留两表所有数据” 的特殊场景(数据对账、全量统计)。

09. 交叉连接

CROSS JOIN,笛卡尔积

  • 写法格式:无关联条件,两表数据完全组合(行数 = 左表行数 × 右表行数),CROSS JOIN可省略;
    -- 示例:用户表(3行)和商品表(2行)交叉连接,结果为6行
    SELECT u.username, p.product_name FROM user u
    CROSS JOIN product p;
  • 性能开销:极高,行数呈指数级增长(1 万行 ×1 万行 = 1 亿行),几乎不用;
  • 适用场景:仅用于 “全组合场景”(如生成所有用户 + 所有商品的测试数据),必须配合WHERE过滤(否则会撑爆内存)。

10. 半连接

半连接不是独立的查询语法,而是 MySQL 5.7 + 默认启用的对列子查询(如IN子查询)的内部优化手段,目的是减少子查询与主查询的匹配开销。

用于优化“主查询匹配子查询结果集”的场景(如IN/EXISTS列子查询)。

核心逻辑是 “只判断匹配与否,不返回子查询的完整数据”,避免逐行匹配的低效问题。

简单说:普通子查询是 “子查询返回所有结果→主查询逐行比对”,半连接是 “主查询与子查询表直接关联→找到匹配行就停止”,本质是将子查询转化为类似JOIN的关联逻辑,提升性能。

触发条件:子查询是“列子查询”(返回单列多行),且子查询表与主查询表无关联(非关联子查询),例如WHERE id IN (SELECT uid FROM t2 WHERE status=1)

写法格式:无特殊语法,仍用普通IN/EXISTS子查询写法,MySQL 会自动触发半连接优化(可通过EXPLAIN查看type列是否有SIMPLE/HASH JOIN,而非SUBQUERY)。

-- 示例:查询有未支付订单的用户(MySQL会自动用半连接优化)
SELECT username FROM user u WHERE u.user_id IN (SELECT DISTINCT o.user_id FROM order o WHERE o.status = 0);

性能开销

  • 低 – 中:远优于未优化的子查询(避免子查询结果集物化后逐行匹配)。
  • 优化逻辑:MySQL 会选择以下方式之一执行半连接:
    • 哈希半连接:对小表(子查询表)建哈希表,主表逐行匹配哈希键(适合大表)。
    • 嵌套循环半连接:小表驱动大表,找到匹配行立即停止扫描(适合小表)。
    • 物化半连接:子查询结果存入临时表并建索引,主表关联临时表(适合子查询结果集较小)。

适用场景

  • 主查询需判断 “字段是否在子查询结果集中”(如IN/EXISTS)。
  • 子查询表数据量适中(1 万行内最佳),且子查询有过滤条件(如WHERE status=1),能减少结果集大小。
  • 替代场景:若子查询结果集极大(10 万行 +),建议手动改写为JOIN(半连接优化效果会下降)。

注意项:

  • 半连接是 MySQL 自动优化,无需专门手动去写语法,但需知道 “IN子查询在 5.7 + 会被优化为半连接”,避免面试官问 “INJOIN哪个快” 时只说表面结论。
  • 实践中,若EXPLAIN显示子查询类型为SUBQUERY(未触发半连接),可通过 “子查询加DISTINCT” 或 “改写为JOIN” 强制优化(如上述示例加DISTINCT让结果集唯一,更易触发半连接)。

总结

1. 子查询 vs 连接查询:

  • 简单场景(如单值匹配、存在性校验)用子查询(写法简洁);
  • 复杂多表关联(如 3 表以上、需筛选多字段)用连接查询(性能更优,易优化);
  • 关联子查询尽量改写为JOIN(避免嵌套循环,如EXISTS适合小表,JOIN适合大表)。

2. 性能优化关键:

  • 连接查询:关联字段必须建索引(如order.user_id),小表驱动大表(减少循环次数);
  • 子查询:避免NOT INNULL 值会导致结果异常),用NOT EXISTS替代;
  • 外连接:右表过滤条件写在ON(关联时过滤),左表过滤条件写在WHERE(关联后过滤)。

3. Java 开发实践场景:

  • 列表查询(如用户列表 + 订单数):用LEFT JOIN + GROUP BY
  • 详情查询(如订单详情 + 用户信息):用INNER JOIN
  • 存在性校验(如判断用户是否有未支付订单):用EXISTS子查询。

02. 子查询与join性能对比

子查询与join性能对比及适用场景

关键结论:

  • 简单场景(单表校验、单值匹配)用子查询(简洁);
  • 多表关联、复杂聚合、主表数据量大时,优先用 JOIN(性能更稳定)。

03. 复杂查询的执行逻辑

1. group by/having

GROUP BYHAVING 是 MySQL 中用于数据分组聚合的核心语法,执行逻辑: “数据过滤→分组→聚合→二次过滤” 。

1. 核心执行逻辑

GROUP BY 的作用是 “按指定字段将数据分组”;
HAVING 则是 “对分组后的结果进行过滤”。

整体执行流程分四步,顺序不可颠倒:

1) 原始数据过滤(WHERE 子句);

  • 先通过 WHERE 筛选符合条件的原始数据(如 WHERE status = 1 过滤无效数据),减少后续处理的数据量。
  • 原理:WHERE 是 “分组前过滤”,仅保留满足条件的行,不涉及聚合操作,可利用索引快速筛选(如 status 字段有索引时,直接定位有效行)。

2) 分组操作(GROUP BY 子句);

  • GROUP BY 后的字段(如 GROUP BY user_id)将上一步过滤后的数据分组,相同值的行被归为一组。
  • 底层处理:
    • 若分组字段有索引,MySQL 会直接按索引顺序分组(无需额外排序,效率高);
    • 若无索引,会创建临时表存储分组数据,或对数据进行文件排序(Using temporaryUsing filesort,可通过 EXPLAIN 查看)。

3) 聚合计算(聚合函数);

  • 对每个分组执行聚合函数(如 COUNT(*) 统计行数、SUM(amount) 计算总和),生成每个分组的聚合结果。
  • 原理:聚合函数仅作用于分组内的数据,每个分组最终输出一行结果(包含分组字段和聚合值)。

4) 分组结果过滤(HAVING 子句);

  • HAVING 筛选聚合后的分组(如 HAVING COUNT(*) > 5 保留行数超 5 的组),最终返回符合条件的分组。
  • 原理:HAVING 是 “分组后过滤”,可直接使用聚合函数(因聚合结果已生成)。

2. 关键区别与实践注意

1) WHERE 与 HAVING 的核心区别

  • WHERE 作用于分组前的原始数据,不能使用聚合函数(如 WHERE COUNT(*) > 5 会报错);
  • HAVING 作用于分组后的结果,可以使用聚合函数(如 HAVING SUM(amount) > 1000 合法)。

2) 性能优化实践(重点)

  • 优先用 WHERE 过滤:尽量在分组前通过 WHERE 减少数据量(如 GROUP BY user_id HAVING user_id > 100 可改为 WHERE user_id > 100 GROUP BY user_id,减少分组计算量)。
  • 分组字段加索引:避免临时表和文件排序(如 GROUP BY create_time 时,给 create_time 建索引,EXPLAIN 中无 Using temporary 即为优化生效)。
  • 避免 SELECT 非分组字段:MySQL 5.7+ 默认开启 ONLY_FULL_GROUP_BY 模式,SELECT 后只能出现 GROUP BY 字段或聚合函数(如 SELECT user_id, username, COUNT(*)username 未在 GROUP BY 中,会报错,需规范写法)。

3. 典型场景举例

  • 统计高频用户:查询 “订单数超 10 单的用户及其总消费”,用 GROUP BY user_id HAVING COUNT(*) > 10
  • 按时间分组分析:查询 “每月订单金额超 10 万的月份”,用 GROUP BY month(create_time) HAVING SUM(amount) > 100000

2. limit 分页

LIMIT 分页是 MySQL 中用于从查询结果中截取指定范围数据的核心语法(如 LIMIT offset, size 取第 offset + 1offset + size 行),其执行逻辑围绕 “全量查询→排序→截取” 展开,实际使用中需重点关注性能问题。

1. 基本执行逻辑

LIMIT 本身不参与数据筛选,而是对 “查询 + 排序后” 的结果集进行截取,核心流程分三步:

1) 执行主查询获取原始数据

  • 先执行 WHEREJOIN 等条件筛选,得到所有符合条件的行(如 SELECT * FROM order WHERE status=1 筛选有效订单)。

2) 排序处理(若有 ORDER BY

  • 若包含 ORDER BY(分页几乎必带,否则结果无序),MySQL 会按指定字段排序:
    • 若排序字段有索引(如 ORDER BY create_timecreate_time 有索引),直接利用索引顺序获取有序数据(效率高);
    • 若无索引,需在内存或磁盘中进行 “文件排序”(Using filesort,可通过 EXPLAIN 查看),排序过程会消耗额外 CPU/IO。

3) 截取指定范围数据

  • 从排序后的完整结果集中,跳过前 offset 行,返回接下来的 size 行(如 LIMIT 20, 10 即跳过前 20 行,返回 10 行)。

2. 核心性能问题:offset 过大导致效率低下

offset 很大时(如 LIMIT 100000, 10),性能会急剧下降,原因是:

  • MySQL 必须先扫描并排序前 100010 行数据,然后丢弃前 100000 行,仅返回最后 10 行,大量计算被浪费;
  • 若排序无索引(依赖文件排序),会产生临时文件,进一步加剧性能损耗(10 万行数据排序可能耗时数百毫秒)。

3. 实践优化方案(重点,体现工程经验)

针对 offset 过大的问题,结合 Java 开发中常见的分页场景(如列表页、历史记录查询),优化手段如下:

1) 基于 “主键 / 唯一索引” 分页(最常用)

利用主键或唯一索引的有序性,通过 WHERE 条件直接定位起始位置,避免全量扫描:

-- 代替 LIMIT 100000, 10(低效)
SELECT * FROM order 
WHERE id > 100000  -- 上一页最后一条数据的id
ORDER BY id 
LIMIT 10;

原理:主键索引是有序的,id > 100000 可直接定位到起始行,无需扫描前 10 万行,性能提升 10 倍以上。

2) 避免 SELECT *,只查必要字段

减少数据传输量和内存占用,尤其大表(如包含 text 字段的表):

-- 只查需要的字段(如订单号、金额、时间)
SELECT order_no, amount, create_time FROM order 
WHERE id > 100000 
LIMIT 10;

3) 确保排序字段有索引

分页必须带 ORDER BY,且排序字段需建索引(如 create_time 索引),避免文件排序:

-- 给 create_time 建索引:CREATE INDEX idx_order_create_time ON order(create_time)
SELECT * FROM order 
WHERE status=1 
ORDER BY create_time DESC  -- 利用索引排序
LIMIT 20, 10;

4) 限制最大分页页数

业务上避免允许用户访问过大页数(如 “只显示前 100 页”),超过则提示 “数据过多,请缩小范围”(如电商平台常见做法),从源头减少大 offset 场景。

04. order by 的排序原理

MySQL 的ORDER BY用于对查询结果按指定字段排序,其核心原理是 “利用索引有序性直接取数” 或 “无索引时通过内存 / 磁盘排序”,性能差异主要源于是否能借助索引避免额外排序操作。

1. 核心排序原理

1. 利用索引排序

Using index,高效。

若排序字段(或多字段排序的前缀)有有序索引(如 B + 树索引,本身按字段值有序存储),MySQL 会直接沿索引顺序读取数据,无需额外排序,这是最优情况。

  • 原理:B + 树索引的叶子节点按索引字段值升序(或降序,取决于建索引时的指定)排列,ORDER BY字段与索引顺序一致时,可直接通过索引定位并返回有序数据,避免排序开销。
  • 示例:
    订单表order有索引idx_create_timecreate_time升序),执行SELECT id, create_time FROM order ORDER BY create_time时,MySQL 直接沿idx_create_time的叶子节点顺序取数,无需排序(EXPLAINExtra列显示Using index)。

2. 文件排序

Using filesort,低效。

若排序字段无索引,或排序顺序与索引顺序不一致(如索引是升序,ORDER BY用降序且无对应降序索引),MySQL 会触发 “文件排序”:先将数据加载到内存 / 临时文件,完成排序后再返回结果。

  • 执行步骤:

    1. 从表中读取符合WHERE条件的行(无索引时全表扫描);
    2. 将 “排序字段值 + 行指针(指向原始数据位置)” 存入sort_buffer(排序缓冲区);
    3. sort_buffer装不下所有数据,会将数据分块,先在内存中排序,再写入临时文件(磁盘);
    4. 最后合并所有分块的排序结果,得到全局有序的结果集,再通过行指针取原始数据返回。
  • 示例:
    订单表ordercreate_time无索引,执行SELECT * FROM order ORDER BY create_time时,MySQL 会先扫描全表,将create_time和行指针存入sort_buffer,排序后再取完整数据(EXPLAINExtra列显示Using filesort)。

2. 影响排序性能的关键因素

  1. 是否使用索引:索引排序性能远高于文件排序(毫秒级 vs 秒级,数据量大时差距更大)。
  2. sort_buffer大小:由sort_buffer_size参数控制(默认 256KB),若数据量超过该值,会触发磁盘临时文件排序(IO 开销剧增)。
  3. 查询字段多少SELECT *会导致sort_buffer中存储更多字段(增加内存占用),而只查必要字段可减少sort_buffer压力,加速排序。

3. 实践优化建议

1. 给排序字段建合适的索引:

  • 单字段排序:直接给排序字段建索引(如ORDER BY create_time → 建idx_create_time)。
  • 多字段排序(如ORDER BY a, b DESC):建联合索引(a, b DESC),索引顺序与排序顺序完全一致(避免索引失效)。

2. 避免SELECT *,只查必要字段:

  • 例如,列表页只需idnamecreate_time,则SELECT id, name, create_time ...,减少sort_buffer中存储的数据量,避免磁盘排序。

3. 控制排序数据量:
先用WHERE过滤无效数据(如WHERE status=1),减少进入排序阶段的行数(如从 100 万行滤到 1 万行,排序效率提升 100 倍)。

4. 大结果集排序用分页配合索引:
如 “按时间排序分页查询订单”,建(create_time, id)联合索引,用WHERE create_time > ?定位分页起点,避免全量排序。

4. 总结

ORDER BY的优化核心是 “能用上索引就避免文件排序”:索引排序依赖 B + 树的有序性,高效低耗;文件排序需内存 / 磁盘排序,性能差。实际开发中,通过 “建匹配索引 + 精简查询字段 + 提前过滤数据” 可显著提升排序性能,这也是处理订单列表、用户日志等需排序场景的常规优化手段。

05. 慢查询的定位与分析

MySQL 的慢查询(执行时间超过阈值的 SQL)是性能优化的核心切入点,定位与分析需结合日志工具、执行计划和业务场景。

1. 慢查询的定位

慢查询的定位:通过日志捕获目标 SQL。

定位慢查询的核心是开启慢查询日志,记录执行时间超标的 SQL,为后续分析提供依据。

1. 慢查询日志配置(5.7 版本)
通过修改my.cnf(或my.ini)配置,开启并定制日志规则:

slow_query_log = 1  # 开启慢查询日志(默认:0,关闭)
slow_query_log_file = /var/log/mysql/slow.log  # 日志文件路径(需MySQL有写入权限)
long_query_time = 1  # 慢查询阈值(单位秒,默认10秒,建议生产设1-2秒)
log_queries_not_using_indexes = 1  # 记录未使用索引的查询(即使未超阈值,可选)
log_output = FILE  # 日志输出方式(FILE/table,5.7默认:FILE)

配置后重启 MySQL 生效,也可通过SET GLOBAL动态开启(无需重启,适合临时排查):

SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;

2. 日志收集工具

  • mysqldumpslow(自带工具):简单统计慢查询,适合快速定位高频问题:

    # 查看访问次数最多的10条慢查询
    mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
    # 查看总执行时间最长的10条慢查询
    mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

    优势:轻量,适合初步筛选;
    不足:无法分析趋势或复杂统计。

  • pt-query-digest(Percona 工具包):生产环境常用,可按执行时间、频率、用户等维度分析,输出 SQL 模板、平均耗时、扫描行数等关键信息:

    pt-query-digest /var/log/mysql/slow.log > slow_analysis.txt

    优势:能识别重复 SQL(如参数不同但结构相同的查询),定位 “隐形高频慢查询”(单条耗时 0.8 秒,但每秒执行 100 次,总耗时 80 秒)。

2. 慢查询的分析

慢查询的分析:通过执行计划定位根因。

捕获慢查询后,需用EXPLAIN分析其执行逻辑,重点关注是否全表扫描、是否用对索引、是否有额外排序 / 临时表。

1. 核心分析工具:EXPLAIN

对慢查询执行EXPLAIN,查看关键字段:

  • type:访问类型(性能从好到差:const > eq_ref > ref > range > ALL)。ALL表示全表扫描(需优先优化)。
  • key:实际使用的索引(NULL表示未用索引)。
  • rows:预估扫描行数(值越大,效率越低)。
  • Extra:额外信息(关键警告:Using filesort(需排序且无索引)、Using temporary(需创建临时表)、Using where; Using filesort(全表扫描后排序,性能极差))。

2. 常见慢查询原因及分析(结合EXPLAIN

  • 全表扫描(type=ALLkey=NULL
    • 原因:查询条件无索引(如:WHERE status=1status无索引),或索引失效(如:WHERE name LIKE '%abc'左模糊匹配导致索引失效)。
  • JOIN 关联无索引(rows极大)
    • 原因:被驱动表的关联字段无索引(如t1 JOIN t2 ON t2.uid = t1.idt2.uid无索引),导致被驱动表全表扫描。
  • 排序无索引(Extra=Using filesort
    • 原因:ORDER BY字段无索引,或排序顺序与索引不一致(如索引是create_time ASC,但查询用ORDER BY create_time DESC且无降序索引)。
  • 子查询嵌套(type=DEPENDENT SUBQUERY
    • 原因:关联子查询导致主表每行触发一次子查询(如WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.uid = t1.id)),无索引时性能骤降。

3. 慢查询的优化方向

分析出原因后,针对性优化:

1. 加索引

  • 为查询条件、JOIN 关联字段、排序字段建索引(如:WHERE+ORDER BY字段建联合索引,如idx_status_create_time (status, create_time))。

2. 改写 SQL

  • 子查询改 JOIN(如关联子查询改写为INNER JOIN,减少嵌套循环);
  • 避免左模糊匹配(LIKE '%abc'改为LIKE 'abc%',或用全文索引);
  • 大分页改范围查询(LIMIT 100000,10改为WHERE id > 100000 LIMIT 10)。

3. 控制数据量

  • WHERE提前过滤无效数据(如WHERE create_time > '2024-01-01'),减少扫描和排序行数。

4. 8.0 版本的优化

  • 慢查询日志支持表存储:除文件外,可将日志写入mysql.slow_log表(log_output = TABLE),支持 SQL 查询分析(如按时间范围统计)。
  • 日志内容更丰富:新增query_id(唯一标识)、rows_affected等字段,方便关联性能模式(Performance Schema)数据。
  • 动态调整更灵活:long_query_time支持小数(如 0.5 秒),且修改后立即生效(无需重启连接)。

5. 总结

慢查询定位与分析的核心流程是:开启日志捕获 → 工具统计筛选 → EXPLAIN分析执行计划 → 针对性优化(索引 / 改写 SQL)

实践中,需结合业务场景(如订单表慢查询多与user_idcreate_time索引相关),优先解决 “高频 + 高耗时” 的慢查询。

06. 复杂SQL的拆分与改写

大表分页优化:延迟关联、书签分页。

MySQL 大表分页是业务中高频痛点(如订单列表、日志查询),普通LIMIT offset, sizeoffset较大时(如LIMIT 100000,10)性能极差:因 MySQL 需扫描前 10 万行再丢弃,仅取最后 10 行,IO 开销剧增。

复杂 SQL 拆分与改写的核心思路是减少无效扫描行数,常用优化手段包括延迟关联书签分页

1. 普通分页的性能瓶颈

普通分页 SQL 示例(订单表order,按create_time逆序):

SELECT * FROM `order` ORDER BY create_time DESC LIMIT 100000, 10;

问题:当offset=100000时,MySQL 需先遍历索引找到第 10 万行的位置(即使有create_time索引),再回表读取 10 行数据,前 10 万行的扫描属于无效开销,数据量越大越慢。

2. 关键优化方法

1. 延迟关联(延迟回表)

概念:先通过覆盖索引获取目标数据的主键(或唯一键),再关联原表获取完整字段,避免 “早期回表” 带来的大量 IO。

原理:利用索引的 “覆盖查询” 特性(只查主键,无需回表)快速定位目标行,再通过主键关联原表取数,减少扫描和回表的数据量。

优化后 SQL 示例:

-- 子查询用索引获取主键(覆盖索引,无需回表)
SELECT o.* 
FROM `order` o 
JOIN (SELECT id FROM `order` ORDER BY create_time DESC LIMIT 100000, 10) tmp 
ON o.id = tmp.id;
  • 优势:子查询仅扫描索引获取id(轻量),关联原表时仅取 10 行完整数据,性能比普通分页提升 5-10 倍(10 万 + offset 场景)。
  • 适用场景:需展示完整字段、支持任意跳页(如第 100 页、第 200 页)的列表。

2. 书签分页(游标分页 / 键集分页)

概念:用 “上一页最后一条记录的唯一标识”(书签,如idcreate_time+id)作为条件,替代offset直接定位下一页起点,完全避免无效扫描。

原理:利用主键 / 唯一索引的有序性,通过WHERE条件跳过前面数据,直接从书签位置取数。

优化后 SQL 示例:假设上一页最后一条记录的create_time='2024-01-01 10:00:00'id=100000,下一页查询:

SELECT * 
FROM `order` 
WHERE create_time < '2024-01-01 10:00:00' 
   OR (create_time = '2024-01-01 10:00:00' AND id < 100000) 
ORDER BY create_time DESC, id DESC 
LIMIT 10;
  • 关键:用create_time+id作为书签(create_time可能重复,需加id保证唯一性),通过索引直接定位,无offset开销。
  • 优势:性能极致(无论翻多少页,扫描行数固定为LIMIT size);
  • 局限:不支持 “跳页”(如直接到第 100 页),仅适合 “上一页 / 下一页”“加载更多” 场景(APP / 移动端常用)。

3. 覆盖索引分页

覆盖索引分页:若查询字段(如id, order_no, create_time)全部包含在索引中,直接用索引查询,无需回表:

-- 索引`idx_create_time_order_no (create_time DESC, order_no, id)`覆盖所有查询字段
SELECT id, order_no, create_time 
FROM `order` 
ORDER BY create_time DESC 
LIMIT 100000, 10;

4. 限制最大 Offset

业务上禁止offset超过阈值(如 10000),引导用户通过筛选条件(如 “按时间范围筛选”)缩小数据范围,避免大 offset 查询。

5. 分表分页

超大规模表(亿级)需水平分表(如按create_time分表),分页时先定位分表,再在分表内分页,避免跨表扫描。

3. 总结

大表分页优化的核心是 “用索引定位替代无效扫描”

  • 需支持跳页选延迟关联,通过覆盖索引减少回表;
  • 移动端 / 加载更多选书签分页,用唯一键直接定位;
  • 极致性能选覆盖索引分页,避免回表开销。

07. JOIN优化

小表驱动大表、 避免 cross join

1. JOIN 优化的核心逻辑

MySQL 中 JOIN 的底层实现以Nested Loop Join(嵌套循环连接)为主:外层循环遍历 “驱动表” 的数据,内层循环用驱动表的字段去 “被驱动表” 中匹配数据。。就像Java里的双层for循环。

优化的核心是减少外层循环次数 + 降低内层匹配成本,这也是 “小表驱动大表” 的原理基础。

2. 优化1:小表驱动大表

1. 原理

“小表” 指参与 JOIN 的数据集更小的表(而非物理表大小)。
用小表做驱动表(外层循环),大表做被驱动表(内层循环),可显著减少外层循环次数,从而降低整体 IO 和匹配开销。

  • 举例:小表有 100 行,大表有 100 万行
    • 小表驱动大表:外层循环 100 次,内层每次匹配大表 → 总匹配 100 次;
    • 大表驱动小表:外层循环 100 万次,内层每次匹配小表 → 总匹配 100 万次;
    • 性能差异一目了然。

2. 实践判断与示例

  • 如何判断 “小表”:
    • 通过EXPLAINrows字段(预估扫描行数),行数少的就是 “小表”;
    • 或业务逻辑中明确数据量更小的表(如字典表、配置表)。
  • SQL 示例(用户表user是小表,订单表order是大表):
-- 推荐:小表user驱动大表order(INNER JOIN中MySQL会自动优化,但LEFT JOIN需显式控制)
SELECT u.username, o.order_no 
FROM user u 
INNER JOIN `order` o ON u.id = o.user_id;

-- LEFT JOIN需注意:左表是驱动表,因此左表必须是小表
SELECT u.username, o.order_no 
FROM user u  -- 小表放左表(驱动表)
LEFT JOIN `order` o ON u.id = o.user_id;

3. 注意点

  • INNER JOIN:MySQL 优化器会自动选择小表作为驱动表,无需手动调整;
  • LEFT JOIN:驱动表固定为左表,因此必须将小表放在左表位置(否则会用大表驱动小表,性能暴跌);
  • RIGHT JOIN:驱动表固定为右表,需将小表放在右表位置(实际开发中建议用LEFT JOIN替代,更符合阅读习惯)。

3. 优化2:避免 CROSS JOIN(笛卡尔积)

1. CROSS JOIN 的危害

CROSS JOIN会返回两张表的笛卡尔积(行数 = 表 A 行数 × 表 B 行数),若表 A 有 1 万行、表 B 有 10 万行,结果会有 10 亿行,直接导致数据库 CPU/IO 打满、查询超时。

2. 常见触发场景

  • 显式使用CROSS JOIN关键字且无ON条件;
  • JOIN时遗漏ON关联条件(如SELECT * FROM A JOIN B);
  • WHERE条件无法过滤笛卡尔积(如关联字段值全为 NULL)。

3. 如何避免

  • 任何JOIN必须加ON关联条件(即使是INNER JOIN);
  • 禁止显式使用无关联条件的CROSS JOIN
  • 若需关联但无直接字段,通过业务逻辑补充关联条件(如时间范围、状态过滤)。

4. 优化补充

1. 被驱动表的关联字段必须加索引
被驱动表的ON字段(如order.user_id)需建索引,否则内层循环会全表扫描,即使小表驱动大表也会很慢。

2. 避免SELECT *
只查询需要的字段,减少数据传输量(尤其大表有大字段如TEXT时)。

3. 提前过滤数据
WHERE先过滤驱动表和被驱动表的无效数据(如WHERE o.status=1),减少参与 JOIN 的行数。

5. 总结

MySQL JOIN 优化的核心是:

  • 小表驱动大表:利用嵌套循环的特性减少外层循环次数,LEFT JOIN需手动控制驱动表;
  • 杜绝笛卡尔积:JOIN必须加ON条件,避免CROSS JOIN
  • 索引兜底:被驱动表的关联字段加索引,是 JOIN 性能的基础保障。

08. count的性能差异

count(1)、count(*)、count(字段)

1. count 的本质

count 的本质:统计 “非 NULL 行数”。

count()是聚合函数,核心作用是统计查询结果集中符合条件的 “非 NULL 行数”,不同参数的差异在于 “统计范围” 和 “是否需要判断 NULL”,这直接决定性能。

2. 三种 count 的区别与性能对比

1. count(*)

统计所有行数(含 NULL)。

逻辑:专门用于统计表 / 结果集的总行数。
MySQL 对其有特殊优化:会优先选择最小的非聚集索引(覆盖索引)扫描,若没有则用聚集索引,避免全表扫描。

特点:包含 NULL 值(因为不判断字段,只统计行数),性能最优。

2. count(1)

统计所有行数(占位符)。

逻辑:用常量1作为占位符,统计所有行数(无论字段是否为 NULL)。
MySQL 优化器会将其与count(*)视为等价,底层执行计划完全相同。

特点:性能与count(*)几乎无差异,属于 “语法糖”。

3. count(字段)

统计字段非 NULL 的行数。

逻辑:需逐行检查该字段是否为 NULL,仅统计非 NULL 的行数。性能分两种情况:

  • 字段是索引字段:用索引扫描(比全表快),但需判断 NULL
  • 字段是非索引字段:全表扫描 + 判断 NULL,性能最差。

特点:性能低于count(*)/count(1),且结果可能不等于总行数(因为字段可能为 NULL)。

3. 性能排序

从快到慢:count(*)count(1) > ount(索引字段) > count(非索引字段)

关键原因:

  • count(*)/count(1)无需判断字段 NULL,且 MySQL 会选最优索引;
  • count(字段)需额外判断 NULL,非索引字段还需全表扫描。

4. 实践建议

  • 统计总行数:优先用count(*)(语义最清晰,MySQL 优化最好),而非count(1)count(主键)
  • 统计字段非 NULL 行数:若需此逻辑,确保字段加索引(如count(user_name)user_name有索引);
  • 避免误区:
    • 认为count(主键)更快:实际count(*)会选更小的索引,比主键索引(聚集索引)扫描更快;
    • 认为count(1)count(*)快:MySQL 优化后两者无区别,count(*)更符合语义。

5. 总结

  • 核心差异:count(*)/count(1)统计总行数(含 NULL),性能最优;count(字段)统计非 NULL 行数,性能较差;
  • 实践选择:统计总数用count(*),统计非 NULL 字段用count(索引字段)
  • 优化关键:利用 MySQL 对count(*)的索引优化,避免count(非索引字段)的全表扫描。

6. 扩展

统计useremailnull的总行数,email是普通索引,是SELECT COUNT(email) FROM user快,还是SELECT COUNT(*) FROM user WHERE email IS NOT NULL快?

答:基本相同,但是不推荐使用后者,推荐使用COUNT(email)。因为这个全程是索引,虽然需要索引扫描,但是后者使用了WHERE条件,同样也是需要进行索引扫描,同时还多出了一步条件过滤。同时COUNT(email)语义更明确。

09. INJOIN哪个快

先明确两者的底层逻辑

  • IN:属于 “子查询 / 值列表匹配”,本质是将子查询结果加载到内存后做匹配(或直接匹配值列表),适合 “单字段匹配” 场景;
  • JOIN:属于 “多表关联”,底层以Nested Loop Join为主(小表驱动大表),通过关联字段索引直接匹配,适合 “多字段关联取值” 场景。

  • IN的优势:语法简单,适合 “单字段匹配” 的简单场景;
  • JOIN的优势:支持多字段关联,大结果集下利用索引更高效,是复杂关联的首选。

10. MySQL的函数

MySQL 函数按功能可分为内置函数(系统提供,重点)和自定义函数(用户编写)。

1. 核心内置函数

1. 字符串函数

处理文本数据。

用于字符串拼接、截取、替换等,是业务中最常用的函数类别。

  • CONCAT(str1, str2,...):字符串拼接(如:拼接用户姓名和手机号:CONCAT(username, '-', mobile));
  • SUBSTRING(str, pos, len):截取子串(如:取手机号后 4 位:SUBSTRING(mobile, 8, 4));
  • REPLACE(str, old, new):替换字符串(如:清理内容中的特殊字符:REPLACE(content, '<', ''));
  • LENGTH(str):获取字符串长度(如:校验用户名长度:WHERE LENGTH(username) > 6)。

实践:用户信息格式化、文本内容清洗场景高频使用。

2. 数值函数

处理数字运算。

用于数值计算、取整、进制转换等。

  • ROUND(num, n):四舍五入(如:金额保留 2 位小数:ROUND(amount, 2));
  • ABS(num):取绝对值(如:计算差值的绝对值:ABS(score1 - score2));
  • CEIL(num)/FLOOR(num):向上 / 向下取整(如:订单数量向上取整:CEIL(total/10));
  • SUM(num)/AVG(num):求和 / 平均值(报表统计核心函数)。

实践:金额计算、数据统计场景必备。

3. 日期时间函数

处理时间数据。

用于时间获取、格式化、差值计算,是业务中仅次于字符串函数的高频函数。

  • NOW()/CURDATE():获取当前时间(含时分秒)/ 当前日期(仅年月日);
  • DATE_FORMAT(date, fmt):时间格式化(如:订单时间转字符串:DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s'));
  • DATEDIFF(date1, date2):计算日期差值(如:用户注册天数:DATEDIFF(NOW(), register_time));
  • DATE_ADD(date, INTERVAL expr unit):时间增减(如:计算 7 天后的日期:DATE_ADD(NOW(), INTERVAL 7 DAY))。

实践:订单时间筛选、会员有效期计算、报表时间维度统计。

4. 聚合函数

数据统计分析。

用于对多行数据进行聚合计算,需结合GROUP BY使用。

  • COUNT(*):统计总行数(用户数、订单数统计);
  • SUM(field):求和(销售额、销量统计);
  • MAX(field)/MIN(field):最大值 / 最小值(最高订单金额、最早注册时间);
  • GROUP_CONCAT(field):分组拼接字符串(如:查询用户的所有订单号:GROUP_CONCAT(order_no))。

实践:报表系统、数据看板的核心函数。

5. 条件函数

逻辑判断。

用于实现 SQL 中的条件分支,替代复杂的WHERE判断。

  • IF(condition, val1, val2):简单条件判断(如:判断用户状态:IF(status=1, '正常', '禁用'));
  • CASE WHEN:复杂条件分支(如:订单状态映射:CASE status WHEN 0 THEN '待支付' WHEN 1 THEN '已支付' ELSE '已取消' END);
  • IFNULL(val, default)NULL 值替换(如:用户昵称为空时显示默认值:IFNULL(nickname, '游客'))。

实践:查询结果格式化、动态状态展示。

6. 其他常用函数

  • UUID():生成唯一标识符(分布式场景临时 ID 生成);
  • INSTR(str, substr):查找子串位置(如:判断内容是否含关键词:INSTR(content, 'MySQL') > 0);
  • CAST(val AS type):类型转换(如:字符串转数字:CAST(score AS UNSIGNED))。

2. 面试高频考点

函数使用的性能陷阱。

1. 函数操作索引字段会导致索引失效

这是面试必问点!若对索引字段使用函数,MySQL 无法使用索引,会触发全表扫描。

  • 反例:WHERE DATE(create_time) = '2024-01-01'(对索引字段create_timeDATE()函数,索引失效);
  • 正例:WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02'(直接匹配字段范围,利用索引)。这个之所以能利用索引,是因为没有使用函数破坏索引的有序性,同时MySQL将给出的查询条件值'2024-01-01''2024-01-02'隐式的转换为了DATETIME类型,从而避免了将整个表的create_time字段转成其他类型,保证了原索引的可用性。

2. 聚合函数的NULL处理

  • COUNT(field)会忽略 NULL 值,COUNT(*)统计所有行(含 NULL);
  • SUM(field)NULL 值会被视为 0,但建议用IFNULL(field, 0)显式处理(避免语义歧义)。

3. 自定义函数的慎用场景

  • 自定义函数(CREATE FUNCTION)可实现复杂逻辑,但执行效率低(无法并行执行),且可能导致锁表,高并发场景建议用存储过程或应用层代码替代。

3. 总结

MySQL 函数是提升 SQL 灵活性的核心工具。

  • 分类记忆:重点掌握字符串、日期、聚合、条件函数的常用用法;
  • 性能陷阱:索引字段避免使用函数,否则索引失效;
  • 实践优先:简单逻辑用内置函数,复杂逻辑优先应用层处理(避免 SQL 臃肿)。

回目录: 《面试笔记:MySQL 相关目录》
上一篇: 《面试笔记:MySQL 相关02 – 索引》

喜欢面试笔记:MySQL 相关03 – SQL语法与查询优化这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

  •  

面试笔记:MySQL 相关02 – 索引

感谢订阅陶其的个人博客!

索引

回目录: 《面试笔记:MySQL 相关目录》
上一篇: 《面试笔记:MySQL 相关01 – 基础核心》
下一篇: 《面试笔记:MySQL 相关03 – SQL语法与查询优化》


01. 各种树的原理和特性

学习树,是为了便于学习索引。索引的核心作用是“加速查询”,而高效的树结构正是实现这一点的关键。

答:

1. “树”形象的理解

可以从“现实中的树”类比数据结构的“树”,它和路边的树长得很像,只是“倒过来”了。

想象一棵简化的苹果树:

  • 最底下的 “树根” 是起点,对应数据结构中树的根节点(只有一个);
  • 从树根往上长的 “树干” 会分杈出 “树枝”,这些树枝就是父节点;
  • 树枝再分杈出更细的枝桠,这些细枝桠就是子节点(一个父节点可以有多个子节点);
  • 最顶端的 “苹果”(没有再分杈的部分),就是叶子节点。
    数据结构里的 “树”,就是这样一种 “分层、有分支” 的结构:
  • 所有节点(根、父、子、叶子)都像 “果实” 一样,存储着数据(或索引);
  • 节点之间的 “连接”(比如根到父、父到子)像 “树枝”,表示数据之间的关系;
  • 整个结构是 “单向” 的:只能从根往下找子节点,不能从子节点反推回根(就像苹果不会自己长回树枝)。

举个更具体的例子:

  • 如果用树存 “班级学生名单”,根节点可以是 “三年级”;
  • 根节点的子节点(父节点)可以是 “三班”“五班”;
  • “三班” 的子节点可以是 “男生组”“女生组”;
  • “男生组” 的子节点(叶子节点)就是具体的学生:“小明”“小李”…

这种结构的核心好处是:查找效率高。
比如想找 “三年级三班男生组的小明”,你不用遍历所有学生,只需从根(三年级)→ 三班 → 男生组 → 小明,一步一步按 “分支” 找,比在一堆乱序的名单里翻快得多。

2. 二叉查找树

“不合适”的树(Binary Search Tree)

特性:每个节点最多 2 个子节点(左小右大),查询时从根节点开始,比当前节点小就走左子树,大就走右子树。

问题:容易 “失衡”。比如插入一串递增数据,会退化成链表(左子树为空,只有右子树),查询效率从 O (logn) 暴跌到 O (n)(和遍历链表一样慢)。

为什么 MySQL 不用:数据库索引需要稳定高效的查询,二叉树的失衡问题无法满足。

3. 平衡二叉树

又叫:AVL树/红黑树
解决 “失衡”,但仍有局限

特性:在二叉查找树基础上,通过旋转保持 “平衡”(左右子树高度差不超过 1),确保查询效率稳定在 O (logn)。

问题:还是 “二叉”(每个节点最多 2 个子节点),导致树的 “高度过高”。
比如存 100 万条数据,平衡二叉树的高度大概是 20 层(2^20≈100 万)。

为什么 MySQL 不用:索引数据存在磁盘上,每次查询需要从磁盘读数据(一次 IO 读一个 “页”,比如 4KB)。树高 20 就意味着最多要读 20 次磁盘,IO 成本太高。

4. B 树

多路平衡查找树,降低高度

特性:

  • “多路”:每个节点可以有多个子节点(比如 100 个),不再是二叉。
  • “平衡”:所有叶子节点在同一层,避免某条路径过长。
  • 节点存 “数据”:每个节点不仅存索引键,还直接存对应的数据(或数据地址)。

优势:高度大大降低。比如每个节点有 100 个子节点,存 100 万数据,树高只需 3 层(100^3=100 万),最多 3 次 IO 就能查到数据,比平衡二叉树高效得多。

为什么 MySQL 不完全用它:范围查询不方便。比如查 “id 从 100 到 1000”,B 树需要回溯父节点找下一个范围,效率低。

5. B + 树

MySQL 索引的 “标准答案”
B + 树是 B 树的变种,专门为数据库索引优化设计,核心特性完美适配索引需求:

非叶子节点只存索引,不存数据:

  • 非叶子节点仅保留索引键(比如 id),不存实际数据,这样一个节点能存更多索引键,子节点数量更多(比如 200 个),树高更低(100 万数据只需 2-3 层),IO 次数更少。

叶子节点存完整数据,且首尾相连:

  • 所有实际数据只存在叶子节点,且叶子节点之间用链表连接(形成有序链表)。

对索引的核心价值:

  • 单值查询快:和 B 树一样,通过索引键快速定位到叶子节点,IO 少。
  • 范围查询无敌:比如查 “id>100”,找到第一个 id=100 的叶子节点后,直接顺着链表往后扫,不用回溯父节点,效率极高(MySQL 中范围查询很频繁,这是关键)。

6. MySQL 为什么选 B + 树做索引?

  1. 树高低,IO 次数少(磁盘读写效率高);
  2. 叶子节点有序相连,范围查询(如 between、in)效率远超其他树;
  3. 非叶子节点只存索引,节点存储密度高,进一步降低树高。

02. 索引的作用

答:

  1. 加速查询,降低IO:索引通过有序结构(如B+树)将分散的数据按索引键排序,查询时无需全表扫描,而是通过索引快速定位目标数据所在的磁盘位置,大幅减少磁盘IO次数(比如从扫描全表100万行降到扫描几十行);

  2. 保证数据唯一性,强化业务约束:通过唯一索引(含主键索引),数据库会强制约束索引键的值不重复,直接实现 “业务唯一标识” 需求(如用户 ID、订单号不可重复),避免手动校验的繁琐和风险。

  3. 优化排序与分组操作:索引本身是有序的,当查询包含order by(排序)、group by(分组)时,可直接利用索引的有序性避免全表数据的额外排序(减少内存 / 磁盘临时表开销),提升这类操作的效率。

03. 常见索引类型及其特性

答:

1. 主键索引

Primary Key

特性:

  • 强制唯一性(表中唯一标识一行数据),且不允许NULL值;
  • 一张表只能有一个主键索引;
  • InnoDB 中主键索引是聚簇索引(叶子节点直接存储整行数据),无需回表,查询效率最高。

适用场景:作为表的唯一标识(如用户 ID、订单 ID),确保数据唯一性并加速行查询。

2. 唯一索引

Unique Index

特性:

  • 确保索引列的值唯一,但允许NULL值(且NULL可出现多次);
  • 一张表可创建多个唯一索引;
  • 基于 B + 树结构,非聚簇索引(叶子节点存主键值,需回表查数据);
  • 查询时找到一个匹配值就停止扫描(无需确认是否有重复),比普通索引少了 “继续校验” 的步骤,回表前的索引定位效率更高。

适用场景:需唯一约束但非主键的字段(如手机号、邮箱,允许未填写即NULL)。

3. 普通索引

Normal Index

特性:

  • 最基础的索引类型,无唯一性约束,允许重复值和NULL;
  • 基于 B + 树结构,非聚簇索引,仅用于加速查询,不影响数据本身的约束;
  • 查询时需扫描所有匹配的索引节点(可能有多个重复值),再批量回表取数据,比唯一索引多了 “扫描重复索引” 的开销。

适用场景:高频查询的非唯一字段(如商品分类、用户昵称),单纯提升查询效率。

4. 联合索引

Composite Index

特性:

  • 由多个字段组合创建(如(a, b, c)),遵循最左前缀原则(查询需包含最左字段才能命中索引);
  • 索引按第一个字段排序,第一个字段相同则按第二个,以此类推;
  • 可覆盖多字段查询,减少回表(如查询a, b时,若联合索引包含a, b,则无需回表);
  • 效率依赖查询条件:仅命中最左前缀且为覆盖索引时(无需回表),效率接近普通索引;若未命中全部前缀或需回表,效率会下降。
  • 整体比普通索引慢的核心:索引结构是多字段排序,定位时需匹配多个字段的有序性,逻辑比单字段索引复杂。

适用场景:多字段组合查询(如 “按用户 ID + 订单状态查询”),需合理设计字段顺序(区分度高的字段放左侧)。

5. 前缀索引

Prefix Index

特性:

  • 仅对字符串字段的前 N 个字符创建索引(如index(name(10))),大幅节省存储空间,但索引选择性低(重复率高);
  • 适用于长字符串(如 URL、长文本),但可能降低索引选择性(重复率升高),且无法用于order by/group by或覆盖索引。
  • 查询时需扫描更多索引节点才能定位目标,且无法使用覆盖索引(必须回表),额外增加 IO 开销。

适用场景:长字符串字段的模糊查询(如like ‘abc%’),平衡空间与查询效率。

6. 全文索引

Full-Text Index

特性:

  • 针对大文本内容(如文章、评论)的关键词搜索,针对大文本分词匹配(而非like的字符匹配,非 B + 树精确查找,需先分词、匹配倒排索引,再关联原表数据);
  • 效率远高于like ‘%关键词%’,但仅支持MATCH() AGAINST()语法,且有最小 / 最大词长限制。
  • 逻辑复杂度远高于其他索引,仅适合关键词检索,单值精确查询效率远低于 B + 树结构的索引。

适用场景:全文检索需求(如博客系统的文章关键词搜索)。

7. 索引效率排序

这些索引类型,按照查询速度和效率,从高到低排序

基于InnoDB引擎、但是精确查询场景:
查询效率从高到低排序:主键索引 > 唯一索引 > 普通索引 > 联合索引(命中最左前缀 + 覆盖索引)> 前缀索引 > 全文索引
但是并非绝对,依旧需要结合使用场景进行判断。
排序前提:默认是「单值精确查询」,若为「范围查询」,主键索引和唯一索引的差距会缩小(范围查询需扫描多个节点)。

04. 聚簇索引与非聚簇索引

答:
聚簇索引与非聚簇索引的核心区别是:数据存储位置
聚簇索引,又叫做聚集索引,它的叶子节点存储整行数据;
非聚簇索引的叶子节点仅存储 “索引键 + 主键值”,需通过主键回表查询完整数据(覆盖索引除外)。

05. 最左前缀原则

答:
联合索引的最左前缀原则:
查询条件必须从联合索引的最左字段开始匹配,且连续匹配,才能用到索引的对应部分。

举个例子:联合索引(name, age, score)

  • 能命中索引的情况(从左到右连续匹配):

    • where name = '张三'(只用name,命中索引的name部分);
    • where name = '张三' and age = 20(用name+age,命中索引的name+age部分);
    • where name = '张三' and age = 20 and score > 90(用name+age+score,命中索引的全部三部分)。
  • 不能命中索引的情况(跳过左字段或不连续):

    • where age = 20(跳过最左的name,完全无法命中);
    • where name = '张三' and score > 90(跳过中间的age,只能命中name部分,score无法利用索引)。

联合索引含范围查询或模糊查询能否命中索引?
对于范围查询(大于、小于等)会导致“当前字段右侧的索引字段失效”,但左侧已匹配的字段仍能命中,(当前字段也能命中);
对于模糊查询,如果是前缀模糊(例如:"快乐大本营",like '快乐%'),能命中当前字段及左侧的索引,但当前字段右侧的索引失效;如果是后缀/全模糊查询(例如:%大本营%乐大%),会导致当前字段及右侧字段的索引都失效,仅当前字段左侧已匹配索引有效。

06. 索引的维护成本

答:
MySQL索引的维护成本本质是“空间换时间”的代价,主要集中在“写入性能损耗”、“存储空间占用”、“索引碎片维护”。索引数量越多,结构越复杂,则维护成本越高。

1. 写入操作(插入/更新/删除)的性能损耗

  • 写入数据时,不仅要修改表数据,还需同步维护索引的 B + 树结构。
  • 插入:可能触发 B + 树节点分裂(如节点满时),需重新组织索引排序;若为唯一索引,还需额外校验唯一性(扫描索引确认无重复)。
  • 更新:若更新的是索引列,需先删除旧索引条目,再插入新索引条目,相当于两次索引操作;非索引列更新不影响索引,但仍需维护聚簇索引的物理顺序(InnoDB)。
  • 删除:不会立即释放索引空间,仅标记为 “删除”,后续需通过碎片整理回收,且可能触发 B + 树节点合并(如节点数据过少时)。

2. 存储空间的额外占用

  • 索引需独立存储 B + 树结构,一张表的索引越多、字段越长,占用的磁盘空间越大。
  • 示例:大表的联合索引、长字符串的前缀索引,可能占用与数据本身相当的存储空间;聚簇索引虽无需额外存数据,但非聚簇索引(如唯一、普通索引)需存储主键值,叠加后空间开销显著。

3. 索引碎片与定期维护开销

  • 频繁插入 / 删除后,B + 树会产生 “空洞”(已删除但未释放的空间),导致索引碎片增多。
  • 碎片会降低查询效率(磁盘 IO 增多),需定期执行维护操作(如OPTIMIZE TABLE、重建索引),这些操作会锁表或占用大量 IO 资源,影响业务高峰期性能。

4. 优化器的决策负担

  • 表中索引过多时,MySQL 优化器需遍历所有可能的索引组合,评估最优查询计划,导致查询解析时间变长(尤其复杂查询场景)。

5. 如何优化维护策略

  • 不同索引的维护成本差异:联合索引 > 普通索引 > 唯一索引 > 主键索引(结构越复杂,维护时 B + 树调整逻辑越繁琐);前缀索引、全文索引的维护成本高于单字段索引(前者需处理部分字符,后者需维护分词倒排索引)。
  • 平衡维护成本的核心原则:避免 “过度索引”,仅为高频查询字段创建索引;优先选择窄索引(短字段、少字段组合),减少存储空间和维护开销;定期监控索引使用率,删除无效索引(如长期未被使用的索引)。

07. 回表查询与索引覆盖

答:
回表查询是 “非聚簇索引查询后需二次查聚簇索引拿完整数据” 的过程;
索引覆盖是 “查询字段(即所需的返回字段)全在索引中,无需二次查询” 的优化场景。
二者是 “需回表” 与 “免回表” 的对立关系,直接影响查询效率。

1. 回表查询(Bookmark Lookup)

1. 定义
当使用非聚簇索引(如唯一索引、普通索引、联合索引)查询时,若查询字段未完全包含在该索引中,需先通过非聚簇索引找到 “索引键 + 主键值”,再用主键值查询聚簇索引(主键索引) ,才能获取整行完整数据,这个 “二次查询” 的过程就是回表。

2. 核心逻辑(结合 InnoDB 引擎)

  • 非聚簇索引的叶子节点仅存储 “索引键 + 主键值”,不存完整行数据。
  • 若查询需要非索引字段(如用唯一索引email查user表的主键id和非索引字段name),必须通过主键值回聚簇索引 “兜底”,才能拿到name。

3. 示例

-- 表结构:id(主键)、email(唯一索引)、name(非索引字段)
select id, name from user where email = 'a@test.com';

第一步:通过唯一索引email(非聚簇)找到主键id=100
第二步:用id=100查询聚簇索引,拿到name字段,完成查询;
这两步共同构成回表查询,额外增加了一次聚簇索引查询的 IO 开销。

2. 索引覆盖(Covering Index)

1. 定义
当查询的所有字段(包括筛选条件、返回字段)都包含在某一个索引中时,则无需回表,仅通过该索引就能获取所有需要的数据,这个索引就是 “覆盖索引”,也叫紧凑索引,对应的查询就是覆盖索引查询

2. 核心优势
避免回表,减少一次磁盘 IO(聚簇索引查询),大幅提升效率;
索引数据量远小于全表数据,查询时扫描的数据量更少。

3. 示例

-- 联合索引:idx_email_name(email, name)(包含email和name字段)
select email, name from user where email = 'a@test.com';

查询的筛选字段email、返回字段name均在联合索引中;
直接通过该联合索引就能拿到所有需要的数据(返回字段),无需回聚簇索引,实现 “一次查询完成”。

3. 补充项

触发条件对比:

  • 回表:非聚簇索引 + 查询字段超出索引范围;
  • 索引覆盖:查询字段(筛选 + 返回)完全匹配某一索引(单字段索引或联合索引)。

实战价值:
索引覆盖是优化回表开销的核心手段,设计索引时可将高频查询字段加入联合索引(如idx_userid_status(user_id, status)),避免回表;
聚簇索引查询天然支持 “索引覆盖”(叶子节点存完整数据),无需回表,这也是其查询效率最高的原因之一。

易混点:
索引覆盖的关键是 “字段全包含”,与索引类型无关(单字段索引、联合索引均可作为覆盖索引);
前缀索引无法实现索引覆盖(仅存字段前 N 个字符,无法返回完整字段值)。

4. 不能触发索引覆盖示例

SELECT id, name, email FROM user WHERE email = 'xxx@qq.com';
-- 在user表中,id是主键,email是唯一索引,name是普通索引

结论:不能触发索引覆盖。
索引覆盖的核心要求是查询的所有字段(筛选条件 + 返回字段)必须完全包含在同一个索引中,跨索引无法实现覆盖。

如何修改才能触发索引覆盖?

  • 创建联合唯一索引 unique index idx_email_name(email, name)
  • 筛选字段email、返回字段emailnameid(主键会自动包含在非聚簇索引中),全部包含在这个联合索引里;
  • 查询时无需回表,直接通过该联合索引就能获取所有需要的数据,触发索引覆盖。

08. 如何合理使用索引?

以业务查询为导向,平衡查询效率与维护成本,避免 “过度索引” 或 “无效索引”。

  1. 优先为 “高频查询字段” 建索引,低频查询不建;
  2. 联合索引需遵循 “最左前缀 + 区分度优先(区分度高,重复值少)” 原则;
  3. 避免索引失效场景,确保索引被正确使用;
  4. 利用 “索引覆盖” 减少回表,提升效率;
  5. 根据字段特性选择合适的索引类型;
  6. 定期维护索引,清理无效索引。

合理使用索引的核心是 “按需设计、避免失效、注重维护”。
“索引不是越多越好,而是越合适越好”。

09. 索引失效场景

MySQL 索引失效的本质是:查询条件破坏了索引的有序性或匹配规则;
常见场景集中在:“字段操作”、“类型不匹配”、“查询语法不当”三类;
避免核心是:让查询条件贴合索引设计规则。

以下是常见索引失效场景 + 避免方法:

1. 对索引字段做函数 / 运算操作

失效场景:查询时对索引字段用函数(如substrdate_format)或数学运算,会导致 MySQL 无法利用索引的有序性,只能全表扫描。

  • 示例:where substr(name, 1, 3) = '张三'(name是普通索引)、where age + 1 = 20(age是普通索引)。

避免方法:将函数 / 运算移到等号右侧,或提前计算结果。

  • 优化后:where name like '张三%'where age = 19

2. 隐式类型转换

失效场景:查询条件中字段类型与传入值类型不匹配,MySQL 会自动做类型转换,导致索引失效。

  • 示例:where phone = '13800138000'(phone是int类型,字符串转数字)、where id = '100'(id是int,字符串转数字)。
    避免方法:确保查询值类型与字段类型完全一致。
  • 优化后:where phone = 13800138000where id = 100

3. 模糊查询(非前缀匹配)

失效场景:like的模糊查询若以%开头(后缀模糊 / 全模糊),会破坏索引有序性,导致索引失效;前缀模糊(%在末尾)可正常使用索引。

  • 示例:where name like '%张三'(后缀模糊,失效)、where name like '%张三%'(全模糊,失效)。
    避免方法:优先用前缀模糊查询;若需全模糊,改用全文索引(如fulltext index)或应用层分词。
  • 优化后:where name like '张三%'(前缀模糊)、MATCH(name) AGAINST('张三')(全文索引)。

4. 联合索引不满足最左前缀原则

失效场景:联合索引(如(a, b, c))需从左到右连续匹配,跳过左侧字段或不连续匹配,会导致索引失效或部分失效。

  • 示例:where b = 2 and c = 3(跳过最左a,全失效)、where a = 1 and c = 3(跳过中间b,仅a部分生效,c失效)。
    避免方法:查询条件需包含联合索引的最左字段,且按索引字段顺序匹配;若高频查询b + c,可单独建联合索引(b, c)
  • 优化后:where a = 1 and b = 2 and c = 3(全生效)、where b = 2 and c = 3(改用(b, c)联合索引)。

5. 范围查询后字段失效

失效场景:联合索引中,某字段用><>=<=between做范围查询后,其右侧的索引字段会失效。

  • 示例:where a = 1 and b > 2 and c = 3(联合索引(a, b, c)c失效)。
    避免方法:将范围查询字段放在联合索引的最右侧;若需多字段范围查询,改用覆盖索引或拆分查询。
  • 优化后:where a = 1 and b > 2(仅用a + b索引)、建联合索引(a, b, c)并确保查询字段覆盖(select a, b, c,避免回表)。

6. or连接非索引字段

失效场景:or连接的查询条件中,若有一个字段无索引,会导致整个查询无法使用索引(MySQL 会选择全表扫描)。

  • 示例:where name = '张三' or address = '北京'name有索引,address无索引,全失效)。
    避免方法:确保or连接的所有字段都有索引;或改用union all拆分查询(需字段一致)。
  • 优化后:where name = '张三' union all where address = '北京'address补建索引)。

7. is not null/not in/not exists

失效场景:对索引字段用is not null(部分场景失效)、not innot exists,会破坏索引的匹配逻辑,导致失效(is null通常可使用索引)。

  • 示例:where name is not nullname是普通索引,可能失效)、where id not in (1,2,3)id是主键索引,大数据量下失效)。
    避免方法:is not null改用union拼接非空结果;not in改用left join ... on ... is null;小数据量not in可接受,大数据量必优化。
  • 优化后:where id in (select id from user) union ...is not null替代)、select * from user u left join tmp t on u.id = t.id where t.id is nullnot in替代)。

8. 索引选择性差(失效等价场景)

失效场景:索引字段重复率极高(如 “性别” 字段,仅男 / 女),MySQL 优化器会判断 “全表扫描比索引查询更快”,主动放弃使用索引。

  • 示例:where gender = '男'gender建了索引,但全表 80% 是男性,索引失效)。
    避免方法:不针对低选择性字段建单字段索引;若需查询,将其作为联合索引的右侧字段(如(age, gender)),通过高选择性字段先过滤。

9. 补充

  1. 验证索引是否失效的核心方法:explain分析执行计划,若key字段为NULL或非目标索引,说明索引失效。
  2. 特殊情况:Innodb的聚簇索引(主键)即使有上述场景(如is not null),通常也不会完全失效,因聚簇索引的物理存储特性,优化器更倾向使用。
  3. 避免失效的核心原则:不破坏索引的有序性、不改变字段的原始形态、让查询条件贴合索引设计。

10. explain执行计划分析

explain 是 MySQL 分析 SQL 执行计划的核心工具。
通过输出 12 个字段(面试重点关注 7 个核心字段),可判断索引是否生效、查询是否全表扫描、是否存在文件排序 / 临时表等性能问题。

1. explain 核心作用

  1. 判断 SQL 是否使用了目标索引(避免索引失效);
  2. 识别全表扫描、文件排序、临时表等低效操作;
  3. 分析表的连接顺序、查询类型(简单 / 复杂查询);
  4. 预估查询扫描的行数,评估查询效率。

2. 7 个核心字段解析

1. type:访问类型

核心,判断查询效率的关键

含义:表示 MySQL 如何访问表中的数据(即查询方式),取值决定查询效率,从优到差排序:
system > const > eq_ref > ref > range > index > ALL

关键取值解读:

  • system:表中只有 1 行数据(如系统表),效率最高(罕见);
  • const:通过主键 / 唯一索引查询,匹配 1 行数据(如where id=100),高效;
  • eq_ref:多表连接时,被连接表通过主键 / 唯一索引匹配,每行只返回 1 行(如join on 主键);
  • ref:通过普通索引 / 联合索引前缀匹配,返回多行匹配数据(如where name=’张三’,name 是普通索引);
  • range:范围查询(><betweenin),只扫描索引的某一范围(比ref差,但比全表扫描好);
  • index:扫描整个索引树(索引全扫描),比ALL好(索引数据量小于全表);
  • ALL:全表扫描(最差),需避免(通常是索引失效或未建索引)。

判断标准:type至少要达到range级别,最优是refconst,出现ALL说明存在性能问题。

2. key:实际使用的索引

含义:表示 MySQL 实际选择的索引(若为NULL,说明未使用任何索引,索引失效或无合适索引);
判断标准:若key不是你设计的目标索引(如预期用idx_email,但keyNULL),说明索引失效,需排查原因(结合索引失效场景)。

3. rows:预估扫描行数

含义:MySQL 优化器预估的、查询需要扫描的行数(非精确值,但可反映效率);
判断标准:行数越少越好,若rows远大于表中实际数据量,可能是统计信息过时(需执行analyze table更新),或索引设计不合理。

4. extra:额外执行信息

核心!暴露性能隐患

含义:记录 SQL 执行的额外操作,重点关注 “好的标识” 和 “坏的标识”:

  • 优质标识:
    • Using index:触发覆盖索引,无需回表(高效,面试加分点);
    • Using index condition:索引下推(ICP),减少回表次数(高效)。
  • 性能隐患标识(必须避免):
    • Using filesort:需在内存 / 磁盘中排序(未利用索引有序性,如order by字段无索引);
    • Using temporary:创建临时表存储中间结果(如group by无索引、多表连接无合适索引,性能极差);
    • Using where:全表扫描后过滤数据(type=ALL时出现,说明无索引可用);
    • Using join buffer:多表连接时未用索引,使用连接缓冲区(低效)。

5. id:查询执行顺序

含义:表示查询中每个select子句的执行顺序(数字越大越先执行,相同数字按从上到下顺序);
应用场景:复杂查询(子查询、join)中,判断表的连接顺序是否合理(如小表驱动大表)。

6. select_type:查询类型

含义:区分简单查询和复杂查询,面试高频取值:

  • SIMPLE:简单查询(无子查询、无 union);
  • SUBQUERY:子查询(select中嵌套select);
  • DERIVED:派生表(from中嵌套select);
  • UNIONunion连接的第二个及以后的查询。

面试价值:说明查询的复杂程度,复杂查询(如多层子查询)可能导致优化器选择低效执行计划,需考虑拆分 SQL。

7. table:当前查询的表

含义:显示 SQL 查询的表名(或别名),多表连接时按id顺序显示表的执行顺序。

加项

  1. 如何判断索引是否生效:
    看key字段是否为目标索引(非NULL),同时type不是ALL/index

  2. 如何判断查询是否高效:
    typerange + keyNULL + rows值小 + extrafilesort/temporary

  3. 常见问题排查:

    • type=ALL + key=NULL:索引失效或未建索引,排查索引失效场景;
    • extrafilesortorder by/group by字段未建索引,需添加索引;
    • extratemporarygroup by无索引或多表连接无合适索引,优化索引设计。
  4. explain extended:在explain基础上增加filtered字段(过滤行数占比),filtered越高说明过滤效果越好;

  5. explain format=json:输出 JSON 格式的详细执行计划,适合复杂查询分析;

  6. 执行计划是 “预估” 而非 “实际”:优化器可能因统计信息过时、索引选择性差等误判,需结合实际执行耗时验证。

11. 升序索引与降序索引

在 MySQL 中,索引的升序(ASC)和降序(DESC)是通过创建索引时指定排序方向来设置的,核心目的是匹配查询中ORDER BY的排序需求,避免额外的文件排序(Using filesort)或反向扫描带来的额外开销。

ORDER BY排序时,如果排序字段存在索引,但是索引的顺序与排序的顺序不一致,则排序时导致优化器选择“反向扫描索引”,性能虽然比文件排序快非常多,但是依旧不如排序和索引同向的效果好。

比如:create_time字段存在索引idx_create_time,索引是升序(ASC)的,但是查询排序时,需要逆序排序(比如ORDER BY create_time DESC),那么优化器选择“反向扫描索引”。

1. 升序 / 降序索引的创建语法

MySQL 中创建索引时,默认是升序(ASC),可显式指定ASC(升序)或DESC(降序)。语法如下:

-- 升序索引(默认,可省略ASC)
CREATE INDEX idx_create_time_asc ON 表名(create_time ASC);

-- 降序索引(显式指定DESC)
CREATE INDEX idx_create_time_desc ON 表名(create_time DESC);

升序索引(ASC:索引中字段值按从小到大排序,适用于ORDER BY 字段 ASC的查询。
降序索引(DESC:索引中字段值按从大到小排序,适用于ORDER BY 字段 DESC的查询(如 “按创建时间逆序排序的列表”)。

这里面还存在着版本差异:

2. 5.7版本

5.7版本的限制与实践。

5.7 版本对降序索引的支持是 “语法层面” 的,实际存储仍为升序,查询时若用ORDER BY 字段 DESC,优化器会 “反向扫描升序索引”(而非直接使用降序索引),虽然比全表扫描 + 文件排序高效,但仍有一定开销。

场景:查询列表经常按create_time逆序排序(ORDER BY create_time DESC
在 5.7 中,即使创建DESC索引,实际效果与ASC索引差异不大,但仍建议显式创建DESC索引(为 8.0 升级兼容,且优化器可能优先选择):

-- 5.7中创建“伪降序索引”(语法支持,实际升序存储)
CREATE INDEX idx_create_time_desc ON order(create_time DESC);

-- 查询时,优化器会反向扫描该索引,避免文件排序
SELECT id, order_no FROM order 
WHERE status = 1 
ORDER BY create_time DESC 
LIMIT 10;

5.7 的替代方案(核心优化)

ORDER BY create_time DESC还需配合WHERE条件(如status=1),建议创建联合索引,将过滤字段放前面,排序字段放后面(显式指定DESC):

-- 联合索引:先过滤(status),再排序(create_time DESC)
CREATE INDEX idx_status_create_time_desc ON order(status, create_time DESC);

-- 该索引可同时优化WHERE和ORDER BY,避免全表扫描和文件排序
SELECT id, order_no FROM order 
WHERE status = 1 
ORDER BY create_time DESC 
LIMIT 10;

3. 8.0 版本

8.0 版本的关键优化:真正支持降序索引。

8.0 版本开始,DESC索引会真正按降序存储,查询时若ORDER BY 字段 DESC,可直接匹配降序索引,无需反向扫描,性能比 5.7 提升明显(尤其大数据量排序场景)。

场景:同样按create_time逆序排序

在 8.0 中,创建DESC索引后,ORDER BY create_time DESC会直接使用该索引,EXPLAINExtra列无Using filesort

-- 8.0中创建“真降序索引”(实际按create_time从大到小存储)
CREATE INDEX idx_create_time_desc ON order(create_time DESC);

-- 查询时直接匹配索引,无需排序
SELECT id, order_no FROM order 
ORDER BY create_time DESC 
LIMIT 10;

联合索引的降序支持(8.0 增强)
8.0 允许联合索引中部分字段指定DESC,更精准匹配复杂排序场景。

例如 “按status升序、create_time降序排序”:

-- 联合索引:status升序,create_time降序
CREATE INDEX idx_status_asc_create_time_desc ON order(status ASC, create_time DESC);

-- 完全匹配ORDER BY,无文件排序
SELECT id, order_no FROM order 
ORDER BY status ASC, create_time DESC 
LIMIT 10;

4. 实践建议

1. 优先匹配查询的排序方向:

  • 若查询常用ORDER BY create_time DESC(如订单列表默认 “最新在前”),直接创建create_time DESC索引(8.0 最佳,5.7 次之)。
  • 若排序字段同时有ASCDESC需求(极少),可分别创建升序和降序索引(但需平衡索引维护成本)。

2. 联合索引的排序字段放最后:

  • 索引遵循 “最左前缀原则”,过滤字段(WHERE条件)放前面,排序字段(ORDER BY)放后面,例如WHERE status=1 ORDER BY create_time DESC,联合索引为(status, create_time DESC)。

3. 避免过度创建降序索引:

  • 降序索引与升序索引的维护成本相同(都会占用磁盘空间,影响写入性能),仅对 “高频逆序排序” 的字段创建降序索引,低频场景无需单独创建(依赖优化器反向扫描即可)。

5. 总结

  • 创建方式:通过ASC(默认)或DESC关键字指定,语法简单。
  • 版本差异:5.7 仅语法支持降序索引(实际升序存储),8.0 真正支持(按降序存储,性能更优)。
  • 核心价值:匹配ORDER BY的排序方向,避免文件排序,尤其在大数据量的列表查询(如订单、日志)中,能显著提升性能。

回目录: 《面试笔记:MySQL 相关目录》
上一篇: 《面试笔记:MySQL 相关01 – 基础核心》
下一篇: 《面试笔记:MySQL 相关03 – SQL语法与查询优化》

喜欢面试笔记:MySQL 相关02 – 索引这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

  •  

面试笔记:MySQL 相关01 – 基础核心

感谢订阅陶其的个人博客!

基础核心

回目录: 《面试笔记:MySQL 相关目录》
下一篇: 《面试笔记:MySQL 相关02 – 索引》


01. 整体架构

以5.7版本为基础,8.0的更改会特别标注。

MySQL 采用分层架构,整体可分为 4 层。
连接层、服务层、存储引擎层、文件系统层。

1. 连接层

客户端连接层

  • 负责接收客户端(如Java程序、Navicat等)的TCP/IP连接请求,处理身份认证(用户名、密码、主机权限校验)。
  • 提供连接池机制,复用已建立的连接(避免频繁 TCP 握手 / 挥手的开销),同时管理连接状态(如空闲超时断开)。
  • 核心组件:连接器(验证用户名密码、权限)、连接池(复用已建立的连接,减少握手开销)。
  • 8.0版本变化:
    • 认证插件升级:默认使用caching_sha2_password替代 5.7 的mysql_native_password,加密强度更高,需客户端(如 JDBC 驱动)适配支持。
    • 连接池优化:增强连接复用效率,减少空闲连接占用的资源,同时支持 “连接属性动态修改”(无需重启连接即可调整部分参数)。

2. 服务层

核心处理层

  • 所有存储引擎共享的核心层,负责 SQL 的 “解析 – 优化 – 执行” 全流程,不直接操作数据,仅通过接口调用存储引擎。
  • 包含查询缓存(默认关闭)、解析器、优化器、执行器等核心逻辑。
  • 统一处理日志(如 binlog)、权限二次校验(执行 SQL 前再次确认权限)等通用逻辑。
  • 核心流程:接收 SQL 后,先查查询缓存(若命中直接返回)→ 解析器生成语法树(检查 SQL 语法)→ 优化器生成最优执行计划(如选择索引、连接方式)→ 执行器调用存储引擎接口执行计划。
  • 8.0版本变化:
    • 移除查询缓存组件:因 5.7 中查询缓存命中率极低(数据更新会清空对应表缓存),8.0 直接删除该模块,简化服务层逻辑,避免无效开销。
    • 优化器增强:架构层面支持更多优化规则(如复杂 JOIN 的执行计划调整)。
    • binlog 默认开启:5.7 中 binlog 默认关闭,8.0 默认启用,且架构上支持 binlog 的 “即时回放”(加速主从同步),无需额外配置。

3. 存储引擎层

数据存储层

  • 采用 “插件式架构”,可动态加载不同存储引擎(如 InnoDB、MyISAM),负责数据的实际存储、读取和事务管理。
  • 与服务层通过统一的handler接口交互,服务层无需关心底层数据存储格式(如 InnoDB 的表空间、MyISAM 的文件存储)。
  • 默认存储引擎为 InnoDB,架构上支持事务、行锁等特性的底层实现。
  • 8.0版本变化:
    • 强化 InnoDB 的架构适配:移除其他低效存储引擎(如 Federated),仅保留 InnoDB、MyISAM、Memory 等常用引擎,聚焦 InnoDB 的性能优化。
    • 架构层面支持 InnoDB 的新特性:如自增 ID 持久化(通过 redo log 架构实现)、隐藏索引(架构上支持索引的 “逻辑禁用”,不影响物理存储)。
    • 锁机制架构优化:行锁的范围判断逻辑在架构层面更精准,减少锁冲突(依赖与服务层的接口交互优化)。

4. 文件系统层

  • 负责将数据、日志等持久化到磁盘,依赖操作系统的文件系统(如 ext4、NTFS)。
  • 存储文件类型包括:表空间文件(InnoDB 的.ibd)、日志文件(redo log(重做日志)、binlog(二进制日志)、undo log(回滚日志))、配置文件(my.cnf)等。
  • 8.0版本变化:
    • 日志文件架构调整:redo log 默认存储路径优化,支持更大的日志文件大小(默认单个文件 1GB,5.7 默认 48MB),提升崩溃恢复效率。
    • 表空间文件优化:默认使用独立表空间(5.7 需手动配置),且架构上支持 “表空间加密”(透明数据加密 TDE),文件存储更安全。
    • 自增 ID 存储架构变更:5.7 中自增 ID 存于内存,8.0 架构上改为写入 redo log,重启后可通过日志恢复自增 ID 序列,避免重复。

02. 核心组件

1. 连接器

Connection Manager
核心功能:负责客户端连接的建立、管理与身份认证,是客户端与 MySQL 交互的 “入口”。

  • 接收 TCP 连接请求后,校验用户名、密码及客户端主机权限(基于mysql.user表),通过后分配连接线程。
  • 维护连接状态(如空闲、活跃),默认空闲超时时间为 8 小时(wait_timeout参数控制),超时后自动断开。
  • 认证依赖mysql_native_password插件(默认),加密强度一般,兼容性好。
  • 8.0 版本变化:
    • 默认认证插件升级为caching_sha2_password,采用 SHA-256 加密,安全性更高(需客户端驱动支持,如 JDBC 需 8.0 + 版本)。
    • 优化连接复用机制,减少空闲连接的资源占用,支持 “连接属性动态修改”(如无需重连即可调整部分会话参数)。

2. 查询缓存

Query Cache
核心功能:缓存 SQL 语句与结果集,相同 SQL(字节级一致)可直接返回缓存结果,减少重复计算。

  • 存在但默认关闭(query_cache_type=OFF),需手动开启;缓存以表为单位,表数据更新(增删改)时会清空该表所有缓存。
  • 局限性明显:仅适用于静态数据(如配置表),高并发写场景下命中率极低,反而因缓存维护消耗资源。
  • 8.0 版本变化:
    • 彻底移除查询缓存组件(相关参数如query_cache_size失效),原因是其实际应用价值低,移除后简化了服务层逻辑,减少无效开销。

3. 解析器

Parser
核心功能:对 SQL 语句进行语法分析,生成 “语法树”,确保 SQL 符合语法规则。

  • 解析过程包括:词法分析(识别关键字、表名、字段名等)→ 语法分析(检查 SQL 结构是否合法,如SELECT后是否有字段、WHERE是否搭配条件)。
  • 若语法错误(如关键字拼写错误),直接返回报错(如 “you have an error in your SQL syntax”)。
  • 8.0 版本变化:
    • 核心功能不变,但扩展了对新语法的支持(如WITH RECURSIVE递归查询、降序索引语法),解析效率略有优化(减少语法树生成的内存占用)。

4. 优化器

Optimizer
核心功能:基于语法树生成 “最优执行计划”,目标是最小化执行成本(CPU、IO 开销)。

  • 优化逻辑包括:选择合适的索引(如判断全表扫描 vs 索引扫描更快)、调整多表连接顺序(小表驱动大表减少中间结果集)、简化表达式(如a=1 and a=2直接判定为无效)。
  • 依赖表统计信息(如行数、数据分布),但统计信息更新不及时可能导致执行计划偏差。
  • 8.0 版本变化:
    • 增强统计信息:引入 “直方图”(Histogram),更精准记录数据分布(如字段值的频率),优化器对非均匀分布数据的索引选择更合理。
    • 优化规则扩展:支持复杂 JOIN(如多表嵌套连接)的执行计划调整,子查询优化更彻底(减少 “派生表” 的临时表开销)。

5. 执行器

Executor
核心功能:根据优化器生成的执行计划,调用存储引擎接口执行操作,并返回结果。

  • 执行前再次校验权限(避免连接建立后权限被修改导致的安全问题)。
    • 例如:检查用户是否有目标表的SELECT权限。
  • 通过统一接口(如handler::read_row)调用存储引擎,获取数据后进行过滤、聚合等处理(如WHERE条件过滤、GROUP BY分组)。
  • 8.0 版本变化:
    • 优化接口调用效率,减少与存储引擎的交互次数(如批量读取数据)。
    • 支持 “即时执行”(Instant Execution),对简单查询可跳过部分优化步骤,直接执行,提升响应速度。

6. 日志组件

核心日志模块
负责记录 MySQL 的操作和状态,支撑数据恢复、主从同步等功能。

1. binlog(二进制日志)

  • 5.7 版本:默认关闭,需手动开启(log_bin=ON)。记录所有数据修改操作(增删改、DDL),格式支持STATEMENT(语句)、ROW(行)、MIXED(混合),用于主从同步和时间点恢复。
  • 8.0 版本:默认开启。格式默认ROW(更安全),支持 “即时回放”(Binlog Instant),主从同步时可跳过部分无效日志,提升同步效率。

2. redo log(重做日志,InnoDB 依赖)

  • 5.7 版本:InnoDB 专属,记录数据页的物理修改,采用 “循环写” 机制(固定大小文件),保证崩溃后数据可恢复(先写日志再写磁盘,即 WAL 机制)。
  • 8.0 版本:默认单个日志文件大小从 48MB 增至 1GB,减少日志切换频率;支持 “并行写入”,提升高并发下的日志写入效率。

3. undo log(回滚日志,InnoDB 依赖)

  • 5.7 版本:记录数据修改前的状态,用于事务回滚和 MVCC(多版本并发控制),默认随表空间存储,可能因长期积累导致空间膨胀。
  • 8.0 版本:支持 “undo log 自动回收”(通过innodb_undo_log_truncate参数),无需手动清理,减少维护成本。

7. 权限组件

核心功能:管理用户权限,控制对数据库、表、字段的操作权限。

  • 权限存储在mysql库的系统表中(如userdbtables_priv),权限修改后需通过FLUSH PRIVILEGES刷新或重启生效(静态权限)。
  • 支持库级、表级、列级权限,但缺乏细粒度的动态权限(如管理特定日志的权限)。
  • 8.0 版本变化:
    • 引入 “动态权限”(如BINLOG_ADMINBACKUP_ADMIN),权限修改后即时生效,无需刷新或重启。
    • 权限检查逻辑优化,结合角色(Role)管理(5.7 后期引入但不完善,8.0 强化),可批量分配权限,简化权限管理。

8. 总结

MySQL 核心组件的核心逻辑(连接、解析、优化、执行、日志、权限)在 5.7 和 8.0 中保持一致。
8.0 的改进集中在:移除低效组件(查询缓存)、增强安全性(认证插件)、优化性能(优化器、日志)、简化维护(动态权限、undo 回收)等。

03. 存储引擎

MySQL 的存储引擎是负责数据存储、读取及底层特性实现的核心模块,采用 “插件式” 设计,不同引擎支持的功能(如事务、锁机制)差异显著。

1. InnoDB(默认)

InnoDB 是 MySQL 最常用(默认)的存储引擎,以事务支持、高并发为核心优势,5.7 和 8.0 均将其作为默认引擎,版本间优化集中在性能、可靠性和功能扩展。

核心通用特性(5.7 和 8.0 共通)

  • 支持事务(ACID 特性):通过 redo log(保证持久性)、undo log(保证原子性和隔离性)实现。
  • 行级锁:仅锁定修改的行(而非全表),适合高并发写场景(如电商订单更新)。
  • 聚簇索引:数据与主键索引物理存储在一起,查询主键时效率极高。
  • 外键约束:支持表间外键关联(如orders表关联users表的user_id)。

5.7 版本 InnoDB 特性

  • 自增 ID(AUTO_INCREMENT):存储在内存中,重启 MySQL 后可能因未持久化导致重复(需依赖 binlog 恢复,但存在风险)。
  • 索引限制:仅语法支持降序索引(DESC),实际仍按升序存储,查询降序数据时需额外排序。
  • 锁机制:间隙锁(Gap Lock)范围较宽泛,可能导致高并发下锁冲突增加(如批量插入相邻 ID 时)。
  • undo log:默认随表空间存储,长期运行后可能因未自动回收导致磁盘空间膨胀(需手动清理)。

8.0 版本 InnoDB 关键改进

  • 自增 ID 持久化:将自增 ID 写入 redo log,重启后可通过日志恢复,彻底解决 5.7 的重复问题。
  • 索引增强:
    • 真正支持降序索引(DESC),查询降序排序数据时无需额外排序,直接使用索引。
    • 新增 “隐藏索引”(INVISIBLE):可标记索引为隐藏(不影响物理存储),用于临时禁用索引测试性能(无需删除重建)。
  • 锁优化:间隙锁范围更精准,减少非必要锁定(如批量插入时仅锁定实际需要的区间),降低锁冲突。
  • undo log 自动回收:支持innodb_undo_log_truncate参数(默认开启),自动收缩过大的 undo log,减少人工维护成本。
  • 表空间加密:支持透明数据加密(TDE),表空间文件(.ibd)加密存储,提升数据安全性。

2. MyISAM

MyISAM 是早期 MySQL 的默认引擎,因不支持事务和行锁,逐渐被 InnoDB 替代,5.7 和 8.0 中仍保留但应用场景有限。

核心通用特性(5.7 和 8.0 共通)

  • 不支持事务和外键:仅适合无需事务保证的场景。
  • 表级锁:写操作(增删改)会锁定全表,读操作需等待写锁释放,并发写性能差。
  • 独立文件存储:数据存于.MYD文件,索引存于.MYI文件,可直接复制文件迁移表。

5.7 版本:仍有部分场景使用(如只读日志表),但已明确不推荐用于核心业务。
8.0 版本:进一步弱化 MyISAM,默认配置下性能优化倾向 InnoDB,且移除了部分对 MyISAM 的冗余支持(如全文索引的部分优化仅针对 InnoDB)。

3. Memory

内存引擎
数据存储在内存中,适合临时数据处理,性能极快但数据易失(重启丢失)。

5.7 版本特性

  • 支持哈希索引(默认)和 B + 树索引,哈希索引适合等值查询(=),不支持范围查询(>、<)。
  • 表大小受max_heap_table_size限制(默认 16MB),超出后会报错。

8.0 版本改进

  • 优化内存分配机制,减少小表的内存浪费。
  • 支持动态调整max_heap_table_size(无需重建表),更灵活适配临时数据大小。

4. CSV

逗号分割值引擎
数据以 CSV 格式文件存储(.csv),适合数据交换(如与 Excel、文本文件交互)。

5.7 版本特性

  • 表结构存于.frm文件,数据存于.csv文件,可直接用文本编辑器查看 / 修改。
  • 不支持索引和事务,仅适合简单的导入导出场景。

8.0 版本改进

  • 增强兼容性:支持 CSV 文件中包含换行符(需特殊处理),减少导入导出时的格式错误。

5. 总结

  • InnoDB:5.7 已具备事务、行锁核心能力,8.0 通过自增 ID 持久化、索引优化、锁细化等提升可靠性和性能,是所有业务的首选。
  • MyISAM:仅适合只读、低并发场景,8.0 中进一步被边缘化。
  • Memory/CSV:作为辅助引擎,8.0 主要优化了易用性(如动态调整内存表大小),核心功能无本质变化。

04. InnoDB存储引擎

InnoDB作为MySQL的默认且常用的搜索引擎,有如下的核心特性:

1. 事务支持(ACID)

  • 完全支持事务的原子性(A)、一致性(C)、隔离性(I)、持久性(D)。

2. 锁机制

  • 支持行级锁 + 间隙锁(Next-Key Lock),行级锁仅锁定修改行(高并发友好),间隙锁防止幻读。
  • 8.0 变化:间隙锁范围更精准,减少非必要锁定(如批量插入相邻 ID 时仅锁实际区间),降低锁冲突概率。

3. 聚簇索引

  • 数据与主键索引物理存储在一起,形成 B + 树结构,主键查询可直接获取数据。

4. 自增 ID(AUTO_INCREMENT)

  • 自增 ID 存储在内存中,MySQL 重启后可能因未持久化导致序列重复(需依赖 binlog 部分恢复)。
  • 8.0 变化:自增 ID 写入 redo log 持久化,重启后可通过日志恢复序列,彻底解决重复问题。

5. 索引特性

  • 支持 B + 树索引(默认)、全文索引、前缀索引;仅语法支持降序索引(DESC),实际仍按升序存储。
  • 8.0 变化:
    • 真正支持降序索引,查询降序排序数据时无需额外排序,直接复用索引。
    • 新增隐藏索引(INVISIBLE),可临时禁用索引(不删除),方便测试索引性能影响。

6. 日志依赖(redo log/undo log)

  • redo log:循环写的物理日志,记录数据页修改,保证崩溃恢复。
  • undo log:记录数据修改前的状态,用于事务回滚和 MVCC(多版本并发控制),默认随表空间存储,易膨胀需手动清理。
  • 8.0 变化:
    • redo log 默认单个文件大小从 48MB 增至 1GB,减少日志切换开销。
    • undo log 支持自动回收(innodb_undo_log_truncate默认开启),无需手动清理。

7. 外键约束

  • 支持表间外键关联(如:orders.user_id关联users.id),保证数据引用完整性。

8. 安全性增强

  • 无表空间加密功能,数据文件(.ibd)以明文存储。
  • 8.0 变化:新增透明数据加密(TDE),支持表空间文件加密存储,提升敏感数据安全性(如用户密码、支付信息表)。

05. 三大范式和反范式

MySQL 的三大范式(1NF、2NF、3NF)是关系型数据库设计的基础原则,目的是减少数据冗余、保证数据一致性、避免插入 / 更新 / 删除异常
反范式是有意打破范式规则,有策略地保留适量冗余,目的是减少多表 JOIN提升查询效率(尤其读多写少场景)。

1. 第一范式(1NF)

第一范式(1NF):字段原子化,不可再分。

  • 核心要求:表中所有字段的值必须是 “原子性” 的(不可再拆分为更小的数据单元)。
  • 目的:避免同一字段存储多维度信息,导致查询和修改混乱。
  • 例子:
    • 反例:用户表的address字段存储 “中国 – 北京 – 朝阳区”(可拆分为countrycitydistrict);
    • 正例:拆分为countrycitydistrict三个字段,每个字段不可再分。
  • 实践:1NF 是基础,几乎所有业务表都需满足(如:订单表的phone字段不存储 “固话 + 手机”,而是单独字段)。

2. 第二范式(2NF)

第二范式(2NF):消除 “部分依赖”,非主属性完全依赖主键。

  • 核心要求:在 1NF 基础上,表的主键必须是 “联合主键”(多字段组成),且所有非主属性必须完全依赖于整个主键,不能仅依赖主键的一部分(即消除 “部分依赖”)。
  • 目的:避免因主键部分字段变化导致的数据异常(如修改部分主键后,非主属性需联动更新)。
  • 例子:
    • 反例:订单项表(联合主键order_id+product_id)中,product_name仅依赖product_id(主键的一部分),属于部分依赖;
    • 正例:product_name应存储在产品表中,订单项表只存product_id,通过关联产品表获取名称(非主属性quantityprice完全依赖order_id+product_id)。
  • 实践:多对多关系的中间表(如 “用户 – 角色” 关联表)需满足 2NF,避免冗余存储角色名称等信息。

3. 第三范式(3NF)

第三范式(3NF):消除 “传递依赖”,非主属性不依赖其他非主属性。

  • 核心要求:在 2NF 基础上,所有非主属性必须直接依赖于主键,不能依赖于其他非主属性(即消除 “传递依赖”)。
  • 目的:避免因某个非主属性变化,导致其他非主属性需联动更新(如 A 依赖主键,B 依赖 A,则 B 传递依赖主键)。
  • 例子:
    • 反例:用户表(主键user_id)中,area_name依赖area_idarea_id依赖user_id,则area_name传递依赖user_id
    • 正例:area_name应存储在区域表中,用户表只存area_id,通过关联区域表获取名称(非主属性仅直接依赖user_id)。
  • 实践:用户表、商品表等核心表需满足 3NF,避免存储 “部门名称”“分类名称” 等可通过关联获取的字段。

4. 反范式

反范式是有意打破范式规则,有策略地保留适量冗余,目的是减少多表 JOIN提升查询效率(尤其读多写少场景)。

  • 核心思路
    通过在表中冗余存储其他表的字段,避免查询时关联多个表(JOIN 操作耗时,尤其大数据量时)。
  • 例子:
    • 电商商品列表页需展示 “商品名称 + 分类名称”,若严格遵循 3NF,需关联product表和category表;
    • 反范式优化:在product表中冗余category_name字段,查询时直接从product表获取,无需 JOIN。
  • 适用场景
    1. 高频查询、低频更新:如商品详情页(查询频繁,分类名称很少修改),冗余后查询性能提升 10 倍以上;
    2. 多表关联复杂:如订单列表需关联用户表、商品表、物流表,冗余 “用户名”“商品名” 后,查询从多表 JOIN 简化为单表查询;
    3. 统计分析场景:报表系统需聚合多维度数据,冗余存储聚合结果(如 “每月销售额”),避免实时计算。
  • 注意事项
    • 冗余字段需同步更新:如category_name修改后,需同步更新product表中的冗余字段(可通过触发器、Java 代码事务保证);
    • 控制冗余范围:只冗余高频查询的核心字段(如名称、状态),避免表过大(如冗余大文本字段会增加存储和 IO 成本)。

5. 总结

  • 范式:适合写多读少、数据一致性要求高的场景(如订单系统、用户中心),通过减少冗余降低更新异常风险;
  • 反范式:适合读多写少、查询性能敏感的场景(如电商列表、报表),通过可控冗余提升查询效率。
  • 实际开发中,很少严格遵循某一范式,而是混合使用(如核心交易表用 3NF 保证一致性,查询表用反范式提升性能)。

06. DDL、DML、DCL、DQL

MySQL 中 DDL、DML、DCL、DQL 是按操作类型划分的四大类 SQL 语言,分别对应:

  • DDL:数据库结构定义;
  • DML:数据操纵;
  • DCL:权限控制;
  • DQL:数据查询。

1. DQL:数据查询语言

Data Query Language。

  • 定义:用于从数据库中查询数据,不修改数据或结构,是业务系统中最频繁的操作。
  • 核心命令:SELECT(含WHEREJOINGROUP BYORDER BYLIMIT等子句)。
  • 作用:从表中提取所需数据,支撑业务展示(如列表页、详情页)、统计分析(如报表)等场景。
  • 实践要点:
    • 是 Java 开发中最常用的 SQL(如查询用户信息、订单列表),需结合索引优化(如WHERE条件加索引、避免SELECT *);
    • 复杂查询(多表关联、聚合)需用EXPLAIN分析执行计划,避免全表扫描或文件排序。

2. DML:数据操纵语言

Data Manipulation Language。

  • 定义:用于修改表中的数据(增、删、改),会改变数据内容,但不改变表结构。
  • 核心命令:
    • INSERT(新增);
    • UPDATE(修改);
    • DELETE(删除)。
  • 作用:处理业务数据的生命周期(如创建订单、更新状态、删除无效记录)。
  • 实践要点:
    • 操作会触发事务(默认自动提交,可通过BEGIN手动控制),需保证原子性(如订单创建时同时扣减库存,失败则回滚);
    • 批量操作优化:INSERTVALUES (),(),()批量插入(比单条循环高效),DELETE/UPDATE避免全表操作(加WHERE条件,如DELETE FROM log WHERE create_time < '2024-01-01');
    • 高频写入场景(如日志)需控制频率,避免锁表影响查询。

3. DDL:数据定义语言

Data Definition Language。

  • 定义:用于定义或修改数据库、表、索引等结构,会改变数据库的元数据(结构信息)。
  • 核心命令:
    • CREATE(创建,如CREATE TABLECREATE INDEX);
    • ALTER(修改,如ALTER TABLE ADD COLUMN);
    • DROP(删除,如DROP TABLE);
    • TRUNCATE(清空表)。
  • 作用:初始化数据库结构(如建表、加字段)、调整表结构(如新增索引、扩展字段)。
  • 实践要点:
    • 执行时可能锁表(尤其ALTER TABLE在 InnoDB 中,大表修改会阻塞读写),生产环境需在低峰期执行,大表建议用在线 DDL 工具(如 pt-online-schema-change);
    • 谨慎使用DROPTRUNCATE(不可逆,TRUNCATE会清空数据且不触发事务回滚);
    • 索引相关 DDL(CREATE INDEX)需评估必要性(索引提升查询但降低写入性能)。

4. DCL:数据控制语言

Data Control Language。

  • 定义:用于管理数据库用户权限和事务控制,控制谁能操作数据、操作范围。
  • 核心命令:`
    • GRANT`(授予权限);
    • REVOKE(撤销权限);
    • COMMIT(提交事务);
    • ROLLBACK(回滚事务)。
  • 作用:保障数据安全(如限制应用账号只能操作指定表)、控制事务一致性。
  • 实践要点:
    • 权限遵循 “最小原则”:应用程序账号只授予SELECT/INSERT/UPDATE等必要权限,禁止DROP/GRANT等高风险权限,避免用 root 账号直接连接业务系统;
    • 事务控制(COMMIT/ROLLBACK)需在 Java 代码中配合业务逻辑(如分布式事务场景,确保多库操作一致性)。

5. 总结

四类语言分工明确:

  • DQL:查数据(业务展示核心);
  • DML:改数据(业务操作核心);
  • DDL:改结构(架构维护核心);
  • DCL:控权限(安全保障核心)。

07. 数据库表字段类型

1. 核心字段类型及适用场景

1. 数值型:适合存储数字(整数、小数)

类型 特点(长度 / 范围) JDK8对应类型 适用场景
TINYINT 1 字节,范围 – 128 ~ 127
(无符号 0 ~ 255)
Byte 状态标识(如status:0 – 禁用、1 – 正常)、
性别(0 – 女、1 – 男)
INT 4 字节,范围 – 21 亿 ~ 21 亿 Integer 普通 ID(如user_idorder_id,中小规模业务足够)、数量(如count
BIGINT 8 字节,范围 ±9e18,
大约±90亿亿
Long 大整数 ID(如分布式 ID、雪花 ID)、
高频增长数据(如万亿级订单量)
DECIMAL(M,D) 高精度小数
(M 总长度,D 小数位)
java.math.BigDecimal 金额(如amountDECIMAL(10,2)表示
最多 10 位,2 位小数)、利率
FLOAT/DOUBLE 单 / 双精度浮点
(有精度损失)
Float/Double 非精确计算(如温度、重量,允许微小误差)

2. 字符串型:适合存储文本

类型 特点(长度 / 范围) JDK8对应类型 适用场景
CHAR(N) 固定长度(N 字节,0 ~ 255),
空格填充
String 长度固定的字符串(如手机号CHAR(11)、身份证号CHAR(18)
VARCHAR(N) 可变长度(0 ~ 65535),
存储实际长度
String 长度不固定的文本(如用户名VARCHAR(50)、地址VARCHAR(200)
TEXT 大文本(最大 64KB) String 较长文本(如商品描述、用户备注,不适合建索引)
MEDIUMTEXT/LONGTEXT 更大文本(16MB/4GB) String 超大文本(如文章内容、日志详情,慎用,会拖慢查询)

3. 日期时间型:适合存储时间

类型 特点(长度 / 范围) JDK8对应类型 适用场景
DATETIME 8 字节,范围 1000 ~ 9999 年,
无时区
java.time.LocalDateTime
(不推荐Date
业务时间(如订单创建时间create_time,不受服务器时区影响)
TIMESTAMP 4 字节,范围 1970 ~ 2038 年,
受时区影响
java.time.LocalDateTime
(不推荐Date
记录系统时间(如最后更新时间update_time,自动随时区转换)
DATE 3 字节,仅日期(年月日) java.time.LocalDate
(不推荐Timestamp
生日、到期日(如birthdayexpire_date
TIME 3 字节,仅时间(时分秒) java.time.LocalTime 时段记录(如会议时长、打卡时间)

4. 特殊类型:针对性场景

类型 特点(长度 / 范围) JDK8对应类型 适用场景
ENUM 枚举(存储整数,显示字符串) String 固定可选值(如pay_typeENUM('WECHAT','ALIPAY','CARD')
SET 集合(多选,最多 64 个值) String 多选项(如tagsSET('hot','new','discount')
BLOB 二进制数据(如图片、文件) byte[](字节数组) 小型二进制(如头像缩略图,大型文件建议存 OSS,库中只存 URL)

2. 经典面试点

1. 数值型:精度与范围陷阱

  • 金额为什么用DECIMAL而非FLOAT
    FLOAT是浮点型,存在精度损失(如0.1 + 0.2 = 0.300000004),而DECIMAL是精确小数,适合金额等强精度场景。

  • INTBIGINT怎么选?
    中小业务(千万级数据)用INT足够;分布式系统(如订单 ID 用雪花 ID,长度 18 位)必须用BIGINT,避免溢出。

2. 字符串型:长度与性能

  • CHARVARCHAR的核心区别?
    CHAR固定长度,查询快但浪费空间(如手机号用CHAR(11)VARCHAR(11)高效,无需计算长度);
    VARCHAR节省空间,但查询需额外解析长度,适合长度波动大的场景(如地址)。

  • 为什么不建议VARCHAR(255)滥用?
    MySQL 中VARCHAR(255)在某些引擎(如 InnoDB)中会按 255 字节分配临时内存,即使实际数据很短,也会浪费内存(尤其排序、JOIN 时),建议按实际需求设长度(如用户名VARCHAR(50))。

  • TEXT类型的坑?
    TEXT字段不适合建索引(即使建索引也只能取前 N 个字符),且查询时会额外 IO,大文本建议拆分表(如商品表存desc_id,关联单独的product_desc表存TEXT内容)。

3. 日期时间型:时区与范围

  • DATETIMETIMESTAMP怎么选?
    业务时间(如订单创建时间)用DATETIME(固定值,不受服务器时区影响);系统时间(如最后修改时间)用TIMESTAMP(自动更新,适配多时区部署)。

  • TIMESTAMP的 2038 年问题?
    因范围是 1970 ~ 2038 年,长期系统需注意(可改用DATETIME规避)。

4. 枚举类型ENUM的利弊

  • 优点:存储高效(用整数存字符串),约束数据合法性(只能选定义的值);
  • 缺点:修改枚举值需ALTER TABLE(DDL 操作,大表阻塞),不适合值频繁变化的场景(如活动状态,建议用TINYINT+ 字典表替代)。

3. 总结

字段类型选择的核心原则:“够用即可,避免冗余

  • 数字优先选小类型(如状态用TINYINT而非INT);
  • 字符串按长度固定与否选CHAR/VARCHAR,避免大文本字段拖累主表;
  • 日期根据时区需求选DATETIME/TIMESTAMP
  • 金额、ID 等关键字段必须保证精度和范围,避免溢出或精度丢失。

回目录: 《面试笔记:MySQL 相关目录》
下一篇: 《面试笔记:MySQL 相关02 – 索引》

喜欢面试笔记:MySQL 相关01 – 基础核心这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

  •  

面试笔记:MySQL 相关目录

感谢订阅陶其的个人博客!

MySQL高频问点

1. 基础核心

  • 整体架构
  • 核心组件
  • 存储引擎
  • InnoDB存储引擎
  • 三大范式和反范式
  • DDL/DML/DCL/DQL
  • 数据库表字段类型

2. 索引

  • 各种树
  • 索引的作用
  • 常见索引类型
  • 聚簇索引与非聚簇索引
  • 联合索引的最左前缀原则
  • 索引的维护成本
  • 回表查询与索引覆盖
  • 如何合理使用索引
  • 索引失效场景
  • explain执行计划分析
  • 升序索引与降序索引

3. SQL语法与查询优化

  • 子查询与连接查询
  • 子查询与join性能对比及适用场景
  • 复杂查询(group by/having、limit分页)的执行逻辑
  • order by 的排序原理
  • 慢查询的定位与分析
  • 复杂SQL的拆分与改写(大表分页优化:延迟关联、书签分页)
  • join优化(小表驱动大表、 避免 cross join)
  • count的性能差异(count(1)、count(*)、count(字段))
  • IN和JOIN哪个快
  • MySQL的函数

4. 事务和锁机制

  • 事务
    • ACID
    • 四大隔离级别的定义及实现
    • 默认隔离级别为何是可重复读?
    • 隔离级别与并发问题(脏读、不可重复读、幻读)的对应关系
  • 锁机制
    • 锁的分类
    • 行锁的触发条件
    • MVCC的原理
    • 死锁的产生原因与定位
    • 避免死锁的策略
    • 高并发下如何减少锁竞争

5. 性能调优

  • MySQL核心配置参数的含义与调优依据
  • 连接池配置
  • InnoDB 缓冲池
  • redo log 与 binlog 的协作
  • 刷盘策略对性能与安全性的影响
  • 磁盘IO优化
  • CPU与内存分配
  • 网络优化

6. 高可用与集群架构

  • 主从复制:
    • 复制原理
    • 复制模式
    • 复制延迟的原因与解决
  • 读写分离:
    • 读写分离的实现
    • 一致性问题
    • 分库分表的必要性与拆分策略
  • 高可用架构
    • 主从切换工具
    • 集群方案的适用场景
    • 云原生

7. 数据安全与运维

  • 备份与恢复:
    • 备份类型的选择
    • 基于binlog的时间点恢复流程
    • 大表备份的性能优化
  • 日志系统:
    • binlog
    • redo log
    • 慢查询日志与错误日志的分析
  • 故障付出:
    • 数据库宕机恢复流程
    • 数据一致性校验
    • 索引损坏的修复

8. 进阶特性与版本差异

  • MySQL 8.0新特性:
    • 窗口函数、
    • CTE、
    • 角色管理与权限细化、
    • innoDB的自增锁优化
  • 特殊场景处理:
    • 大表DDL
    • JSON类型的存储与查询优化
    • 地理信息(GIS)功能的实战应用

喜欢面试笔记:MySQL 相关目录这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

  •  

安装MySQL8

1.下载

打开官网下载页面: https://downloads.mysql.com/archives/community/,在这里我选择8.0.25版本,Operating System一栏通常没有CentOS,选择RedHat即可,OS Version选择7,X86_64,然后下载第一个mysql-8.0.25-1.el7.x86_64.rpm-bundle.tar

下载完成后,将文件移动到/opt下,并解压,会解压出多个rpm软件包

[root@localhost opt]# tar -xvf mysql-8.0.25-1.el7.x86_64.rpm-bundle.tar mysql-community-client-8.0.25-1.el7.x86_64.rpmmysql-community-client-plugins-8.0.25-1.el7.x86_64.rpmmysql-community-common-8.0.25-1.el7.x86_64.rpmmysql-community-devel-8.0.25-1.el7.x86_64.rpmmysql-community-embedded-compat-8.0.25-1.el7.x86_64.rpmmysql-community-libs-8.0.25-1.el7.x86_64.rpmmysql-community-libs-compat-8.0.25-1.el7.x86_64.rpmmysql-community-server-8.0.25-1.el7.x86_64.rpmmysql-community-test-8.0.25-1.el7.x86_64.rpm

2.安装前准备

2.1 卸载自带的mariadb

安装基于CentOS 7.9.2009

[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)

CentOS 7.9系统预装了一个mariadb数据库,这个数据库是MySQL的”姐妹”,都是同一个作者编写的,mariadb会在/etc目录生成一个my.cnf作为配置文件,MySQL安装后也会在/etc目录生成一个my.cnf,因此为了排除干扰,首先要把预装的mariadb卸载掉。

查看是否预装:

[root@localhost ~]# rpm -qa | grep -i mariamariadb-libs-5.5.68-1.el7.x86_64
[root@localhost ~]# ls /etc/ | grep mymy.cnfmy.cnf.d

卸载mariadb

rpm -e --nodeps mariadb-libs

2.2 安装依赖

截至目前,CentOS7的yum源已经无法访问,因此需要先更换为阿里巴巴的yum源

cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache

然后安装依赖项,我采用的系统版本需要额外安装这两个依赖,缺失这两个将导致后续安装mysql-community-server时出错

yum install -y net-toolsyum install -y perl

3.安装和初始化

cd进入/opt目录安装刚刚解压出的软件包,不需要每个都安装,仅安装MySQL的Server和Client以及依赖的包,安装需要按照顺序,因为rpm包有相互的依赖关系,需要安装的包以及安装顺序如下:

  1. mysql-community-common-8.0.25-1.el7.x86_64.rpm
  2. mysql-community-client-plugins-8.0.25-1.el7.x86_64.rpm
  3. mysql-community-libs-8.0.25-1.el7.x86_64.rpm
  4. mysql-community-client-8.0.25-1.el7.x86_64.rpm
  5. mysql-community-server-8.0.25-1.el7.x86_64.rpm

使用rpm -ivh命令安装这些包

[root@localhost opt]# rpm -ivh mysql-community-common-8.0.25-1.el7.x86_64.rpm警告:mysql-community-common-8.0.25-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-common-8.0.25-1.e################################# [100%][root@localhost opt]# rpm -ivh mysql-community-client-plugins-8.0.25-1.el7.x86_64.rpm'警告:mysql-community-client-plugins-8.0.25-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-client-plugins-8.################################# [100%][root@localhost opt]# rpm -ivh mysql-community-libs-8.0.25-1.el7.x86_64.rpm警告:mysql-community-libs-8.0.25-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-libs-8.0.25-1.el7################################# [100%][root@localhost opt]# rpm -ivh mysql-community-client-8.0.25-1.el7.x86_64.rpm警告:mysql-community-client-8.0.25-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-client-8.0.25-1.e################################# [100%][root@localhost opt]# rpm -ivh mysql-community-server-8.0.25-1.el7.x86_64.rpm警告:mysql-community-server-8.0.25-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-server-8.0.25-1.e################################# [100%]

安装完成,查看一下安装的版本

[root@localhost opt]# mysql --versionmysql  Ver 8.0.25 for Linux on x86_64 (MySQL Community Server - GPL)[root@localhost opt]# mysqladmin --versionmysqladmin  Ver 8.0.25 for Linux on x86_64 (MySQL Community Server - GPL)

为了保证数据库目录与文件的所有者为mysql登录用户,如果是以root身份运行mysql服务,需要执行下面的命令初始化:

mysqld --initialize --user=mysql

说明: --initialize选项默认以安全模式来初始化,则会为root用户生成一个密码并将该密码标记为过期,登录后你需要设置一个新的密码。生成的临时密码会往日志中记录一份,查看密码:

cat /var/log/mysqld.log
[root@localhost opt]# mysqld --initialize --user=mysql[root@localhost opt]# cat /var/log/mysqld.log2025-04-06T10:34:02.749835Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.25) initializing of server in progress as process 94792025-04-06T10:34:02.921735Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.2025-04-06T10:34:06.304242Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.2025-04-06T10:34:10.865584Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: /*E.4-*jf_#)[root@localhost opt]# 

接下来启动服务

systemctl start mysqld
[root@localhost opt]# systemctl status mysqld● mysqld.service - MySQL Server   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)   Active: active (running) since 日 2025-04-06 18:39:55 CST; 7s ago     Docs: man:mysqld(8)           http://dev.mysql.com/doc/refman/en/using-systemd.html  Process: 9531 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS) Main PID: 9556 (mysqld)   Status: "Server is operational"   CGroup: /system.slice/mysqld.service           └─9556 /usr/sbin/mysqld4月 06 18:39:46 localhost.localdomain systemd[1]: Starting MySQL Server...4月 06 18:39:55 localhost.localdomain systemd[1]: Started MySQL Server.

4.登录和基础配置

4.1 登录并重置密码

用之前生成的初始密码登录MySQL的root用户,命令是:mysql -uroot -p,Enter password:后输入密码,登录成功并进入MySQL交互命令行,但是不能操作,提示需要重置root密码

[root@localhost opt]# mysql -uroot -pEnter password: Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 8Server version: 8.0.25Copyright (c) 2000, 2021, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.mysql> 

执行这个语句重置密码

alter user 'root'@'localhost' identified by 'lzj';

4.2 打开远程连接

安装好的MySQL会自带4个数据库,连接配置的表位于mysql库的user表上,use mysql;将当前库切换为mysql,通过select host,user from user;查看得知系统中的4个用户都仅允许本机访问

mysql> show databases;+--------------------+| Database           |+--------------------+| information_schema || mysql              || performance_schema || sys                |+--------------------+4 rows in set (0.65 sec)mysql> use mysql;Database changedmysql> select host,user from user;+-----------+------------------+| host      | user             |+-----------+------------------+| localhost | mysql.infoschema || localhost | mysql.session    || localhost | mysql.sys        || localhost | root             |+-----------+------------------+4 rows in set (0.01 sec)

通过执行以下语句修改root用户可访问的地址,可以采用通配符,按自己需要修改即可,我选择直接修改为%

允许root用户从192.168.1.x主机访问

update mysql.user set host ='192.168.1.%' where user = 'root';

允许root用户从一切远程主机访问

update mysql.user set host ='%' where user = 'root';

修改完成,刷新权限,即可远程连接

flush privileges;

远程连接前一定要关闭CentOS7自带的防火墙firewalld

4.3 修改字符集

在MySQL8.0版本之前,默认字符集为latin1,utf8字符集指向的是utf8mb3。网站开发人员在数据库设计的时候往往会将编码修改为utf8字符集,如果遗忘修改默认的编码,就会出现乱码的问题,从MySQL8.0开始,数据库的默认编码将改为utf8mb4,从而避免上述乱码的问题。

mysql> show variables like '%char%';+--------------------------+--------------------------------+| Variable_name            | Value                          |+--------------------------+--------------------------------+| character_set_client     | utf8mb4                        || character_set_connection | utf8mb4                        || character_set_database   | utf8mb4                        || character_set_filesystem | binary                         || character_set_results    | utf8mb4                        || character_set_server     | utf8mb4                        || character_set_system     | utf8mb3                        || character_sets_dir       | /usr/share/mysql-8.0/charsets/ |+--------------------------+--------------------------------+8 rows in set (0.12 sec)
  •  

安装MySQL5.7

1.下载

打开官网下载页面: https://downloads.mysql.com/archives/community/,在这里我选择5.7.28版本,Operating System一栏通常没有CentOS,选择RedHat即可,OS Version选择7,X86_64,然后下载第一个mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar

下载完成后,将文件移动到/opt下,并解压,会解压出多个rpm软件包

[root@localhost opt]# tar -xvf mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar mysql-community-embedded-5.7.28-1.el7.x86_64.rpmmysql-community-libs-compat-5.7.28-1.el7.x86_64.rpmmysql-community-devel-5.7.28-1.el7.x86_64.rpmmysql-community-embedded-compat-5.7.28-1.el7.x86_64.rpmmysql-community-libs-5.7.28-1.el7.x86_64.rpmmysql-community-test-5.7.28-1.el7.x86_64.rpmmysql-community-common-5.7.28-1.el7.x86_64.rpmmysql-community-embedded-devel-5.7.28-1.el7.x86_64.rpmmysql-community-client-5.7.28-1.el7.x86_64.rpmmysql-community-server-5.7.28-1.el7.x86_64.rpm

2.安装前准备

2.1 卸载自带的mariadb

安装基于CentOS 7.9.2009

[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)

CentOS 7.9系统预装了一个mariadb数据库,这个数据库是MySQL的”姐妹”,都是同一个作者编写的,mariadb会在/etc目录生成一个my.cnf作为配置文件,MySQL安装后也会在/etc目录生成一个my.cnf,因此为了排除干扰,首先要把预装的mariadb卸载掉。

查看是否预装:

[root@localhost ~]# rpm -qa | grep -i mariamariadb-libs-5.5.68-1.el7.x86_64
[root@localhost ~]# ls /etc/ | grep mymy.cnfmy.cnf.d

卸载mariadb

rpm -e --nodeps mariadb-libs

2.2 安装依赖

截至目前,CentOS7的yum源已经无法访问,因此需要先更换为阿里巴巴的yum源

cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache

然后安装依赖项,我采用的系统版本需要额外安装这两个依赖,缺失这两个将导致后续安装mysql-community-server时出错

yum install -y net-toolsyum install -y perl

3.安装和初始化

cd进入/opt目录安装刚刚解压出的软件包,不需要每个都安装,仅安装MySQL的Server和Client以及依赖的包,安装需要按照顺序,因为rpm包有相互的依赖关系,需要安装的包以及安装顺序如下:

  1. mysql-community-common-5.7.28-1.el7.x86_64.rpm
  2. mysql-community-libs-5.7.28-1.el7.x86_64.rpm
  3. mysql-community-client-5.7.28-1.el7.x86_64.rpm
  4. mysql-community-server-5.7.28-1.el7.x86_64.rpm

使用rpm -ivh命令安装这些包

[root@localhost opt]# rpm -ivh mysql-community-common-5.7.28-1.el7.x86_64.rpm警告:mysql-community-common-5.7.28-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-common-5.7.28-1.e################################# [100%][root@localhost opt]# rpm -ivh mysql-community-libs-mysql-community-libs-5.7.28-1.el7.x86_64.rpm         mysql-community-libs-compat-5.7.28-1.el7.x86_64.rpm[root@localhost opt]# rpm -ivh  mysql-community-libs-5.7.28-1.el7.x86_64.rpm警告:mysql-community-libs-5.7.28-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-libs-5.7.28-1.el7################################# [100%][root@localhost opt]# rpm -ivh  mysql-community-client-5.7.28-1.el7.x86_64.rpm警告:mysql-community-client-5.7.28-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-client-5.7.28-1.e################################# [100%][root@localhost opt]# rpm -ivh  mysql-community-server-5.7.28-1.el7.x86_64.rpm警告:mysql-community-server-5.7.28-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY准备中...                          ################################# [100%]正在升级/安装...   1:mysql-community-server-5.7.28-1.e################################# [100%]

通过rpm -qa已经能看到装上去的包了

[root@localhost opt]# rpm -qa | grep mysqlmysql-community-libs-5.7.28-1.el7.x86_64mysql-community-common-5.7.28-1.el7.x86_64mysql-community-client-5.7.28-1.el7.x86_64mysql-community-server-5.7.28-1.el7.x86_64

查看一下安装的版本

[root@localhost opt]# mysql --versionmysql  Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using  EditLine wrapper

为了保证数据库目录与文件的所有者为mysql登录用户,如果是以root身份运行mysql服务,需要执行下面的命令初始化:

mysqld --initialize --user=mysql

说明: --initialize选项默认以安全模式来初始化,则会为root用户生成一个密码并将该密码标记为过期,登录后你需要设置一个新的密码。生成的临时密码会往日志中记录一份,查看密码:

cat /var/log/mysqld.log
[root@localhost opt]# mysqld --initialize --user=mysql[root@localhost opt]# cat /var/log/mysqld.log2025-04-06T12:26:08.004754Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).2025-04-06T12:26:08.339163Z 0 [Warning] InnoDB: New log files created, LSN=457902025-04-06T12:26:08.403698Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.2025-04-06T12:26:08.470936Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 5151ca04-12e2-11f0-8409-000c29920d74.2025-04-06T12:26:08.472919Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.2025-04-06T12:26:10.249602Z 0 [Warning] CA certificate ca.pem is self signed.2025-04-06T12:26:10.762512Z 1 [Note] A temporary password is generated for root@localhost: G*kRrqwYn7KX

接下来启动服务

systemctl start mysqld
[root@localhost opt]# systemctl status mysqld● mysqld.service - MySQL Server   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)   Active: active (running) since 日 2025-04-06 20:27:54 CST; 8s ago     Docs: man:mysqld(8)           http://dev.mysql.com/doc/refman/en/using-systemd.html  Process: 1468 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS (code=exited, status=0/SUCCESS)  Process: 1451 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS) Main PID: 1471 (mysqld)   CGroup: /system.slice/mysqld.service           └─1471 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid4月 06 20:27:53 localhost.localdomain systemd[1]: Starting MySQL Server...4月 06 20:27:54 localhost.localdomain systemd[1]: Started MySQL Server.

4.登录和基础配置

4.1登录并重置密码

用之前生成的初始密码登录MySQL的root用户,命令是:mysql -uroot -pEnter password:后输入密码回车,登录成功并进入MySQL交互命令行,但是不能操作,提示需要重置root密码

[root@localhost opt]# mysql -uroot -pEnter password: Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 2Server version: 5.7.28Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

执行这个语句重置密码

alter user 'root'@'localhost' identified by 'lzj';

4.2 打开远程连接

安装好的MySQL会自带4个数据库,连接配置的表位于mysql库的user表上,use mysql;将当前库切换为mysql,通过select host,user from user;查看得知系统中的4个用户都仅允许本机访问

mysql> show databases;+--------------------+| Database           |+--------------------+| information_schema || mysql              || performance_schema || sys                |+--------------------+4 rows in set (0.00 sec)mysql> use mysql;Database changedmysql> select host,user from user;+-----------+---------------+| host      | user          |+-----------+---------------+| localhost | mysql.session || localhost | mysql.sys     || localhost | root          |+-----------+---------------+3 rows in set (0.00 sec)

通过执行以下语句修改root用户可访问的地址,可以采用通配符,按自己需要修改即可,我选择直接修改为%

允许root用户从192.168.1.x主机访问

update mysql.user set host ='192.168.1.%' where user = 'root';

允许root用户从一切远程主机访问

update mysql.user set host ='%' where user = 'root';

修改完成,刷新权限,即可远程连接

flush privileges;

远程连接前一定要关闭CentOS7自带的防火墙firewalld

4.3 修改字符集

在MySQL8.0版本之前,默认字符集为latin1,utf8字符集指向的是utf8mb3。网站开发人员在数据库设计的时候往往会将编码修改为utf8字符集,如果遗忘修改默认的编码,就会出现乱码的问题,从MySQL8.0开始,数据库的默认编码将改为utf8mb4,从而避免上述乱码的问题。

查看当前字符集

show variables like '%char%';
mysql> show variables like '%char%';+--------------------------+----------------------------+| Variable_name            | Value                      |+--------------------------+----------------------------+| character_set_client     | utf8                       || character_set_connection | utf8                       || character_set_database   | latin1                     || character_set_filesystem | binary                     || character_set_results    | utf8                       || character_set_server     | latin1                     || character_set_system     | utf8                       || character_sets_dir       | /usr/share/mysql/charsets/ |+--------------------------+----------------------------+8 rows in set (0.00 sec)

字符集不支持中文,因此MySQL5.7需要修改服务端字符集,修改字符集的配置文件位于/etc/my.cnf,将配置添加在[mysqld]节点上

[mysqld]character-set-server = utf8

修改后新建的库和表才会使用utf8,之前建的库表的字符集不会被自动修改,需要手动修改:

⚠️ utf8兼容latin1,因此之前的数据支持修改字符集为utf8,如果不兼容就可能出现乱码问题,不兼容的情况可以将数据导出在重新插入

修改库

alter database db_xxx character set 'utf8';

修改表

alter table tb_xxx convert to character set 'utf8';
  •  

RockyLinux9环境下编译MySQL8

Linux版本: Rocky Linux release 9.5 (Blue Onyx)

1.下载

打开MySQL-Community-Server官方下载页面:https://downloads.mysql.com/archives/community/

筛选出要下载的版本,Product Version选择8.0.39,Operating System选择Source Code,OS Version选择All Operating Systems,然后选择最后一个自带boost的MySQL源码包All Operating Systems (Generic) (Architecture Independent), Compressed TAR Archive
Includes Boost Headers (mysql-boost-8.0.39.tar.gz),进行下载,并将下载到的源码包先放到服务器的/opt目录下。

下载链接:

解压tar包,得到文件夹mysql-8.0.39,里面就是MySQL源码和一些构建信息

[root@localhost opt]# cd mysql-8.0.39/[root@localhost mysql-8.0.39]# lltotal 684-rw-r--r--.  1 7161 31415  94660 Jul 13 03:15 CMakeLists.txtdrwxr-xr-x.  2 7161 31415     65 Jul 13 03:43 Docs-rw-r--r--.  1 7161 31415   5425 Jul 13 03:15 Doxyfile-ignored-rw-r--r--.  1 7161 31415 123326 Jul 13 03:15 Doxyfile.in-rw-r--r--.  1 7161 31415    333 Jul 13 03:15 INSTALL-rw-r--r--.  1 7161 31415 282186 Jul 13 03:15 LICENSE-rw-r--r--.  1 7161 31415    118 Jul 13 03:15 MYSQL_VERSION-rw-r--r--.  1 7161 31415    666 Jul 13 03:15 READMEdrwxr-xr-x.  3 7161 31415     26 Jul 13 03:45 boostdrwxr-xr-x. 10 7161 31415   4096 Jul 13 03:43 clientdrwxr-xr-x.  4 7161 31415   4096 Jul 13 03:43 cmakedrwxr-xr-x. 16 7161 31415   4096 Jul 13 03:43 components-rw-r--r--.  1 7161 31415  10843 Jul 13 03:15 config.h.cmake-rw-r--r--.  1 7161 31415  23642 Jul 13 03:15 configure.cmakedrwxr-xr-x.  3 7161 31415    124 Jul 13 03:43 doxygen_resourcesdrwxr-xr-x. 19 7161 31415   4096 Jul 13 03:43 extradrwxr-xr-x.  4 7161 31415   8192 Jul 13 03:43 includedrwxr-xr-x.  5 7161 31415    112 Jul 13 03:43 libbinlogeventsdrwxr-xr-x.  3 7161 31415     39 Jul 13 03:43 libbinlogstandalonedrwxr-xr-x.  4 7161 31415     54 Jul 13 03:43 libchangestreamsdrwxr-xr-x.  7 7161 31415   4096 Jul 13 03:43 libmysqldrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:43 libservicesdrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:45 mandrwxr-xr-x. 10 7161 31415   4096 Jul 13 03:43 mysql-testdrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:43 mysysdrwxr-xr-x. 10 7161 31415    131 Jul 13 03:43 packagingdrwxr-xr-x. 25 7161 31415   4096 Jul 13 03:43 plugindrwxr-xr-x.  6 7161 31415    155 Jul 13 03:43 router-rw-r--r--.  1 7161 31415   6268 Jul 13 03:15 run_doxygen.cmakedrwxr-xr-x.  4 7161 31415   4096 Jul 13 03:43 scriptsdrwxr-xr-x.  3 7161 31415    155 Jul 13 03:43 sharedrwxr-xr-x. 24 7161 31415  24576 Jul 13 03:44 sqldrwxr-xr-x.  3 7161 31415   4096 Jul 13 03:43 sql-commondrwxr-xr-x. 14 7161 31415   4096 Jul 13 03:45 storagedrwxr-xr-x.  3 7161 31415   4096 Jul 13 03:43 stringsdrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:43 support-filesdrwxr-xr-x.  2 7161 31415    101 Jul 13 03:43 testclientsdrwxr-xr-x.  5 7161 31415     70 Jul 13 03:43 unittestdrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:43 utilitiesdrwxr-xr-x.  2 7161 31415   4096 Jul 13 03:43 vio

和编译MySQl5.7一样,进入boost/文件夹下,查看boost是哪个版本,一会编译时会用到。

[root@localhost mysql-8.0.39]# cd boost/[root@localhost boost]# lltotal 0drwxr-xr-x. 3 7161 31415 19 Jul 13 03:45 boost_1_77_0

2.编译安装前准备

1.新添加一个用户: mysql

useradd mysql

2.安装编译所需依赖

dnf install -y make cmake gcc gcc-c++ ncurses-devel  openssl-devel libtirpc

还有些不能通过yum安装的软件包要手动下载安装

yum install -y wgetwget https://dl.rockylinux.org/pub/rocky/9/devel/x86_64/os/Packages/l/libtirpc-devel-1.3.3-9.el9.x86_64.rpmwget https://dl.rockylinux.org/pub/rocky/9/AppStream/x86_64/os/Packages/r/rpcgen-1.4-9.el9.x86_64.rpmrpm -ivh libtirpc-devel-1.3.3-9.el9.x86_64.rpmrpm -ivh rpcgen-1.4-9.el9.x86_64.rpm

然后再安装MySQL8.0.39需要的gcc工具集,安装前,需要升级软件包libsemanage为高版本,同样需要下载软件包到本地,然后rpm -Uvh更新

wget https://dl.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/Packages/l/libsemanage-3.6-2.1.el9_5.x86_64.rpmrpm -Uvh libsemanage-3.6-2.1.el9_5.x86_64.rpm

然后执行安装gcc工具集命令

yum install gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils gcc-toolset-12-annobin-annocheck gcc-toolset-12-annobin-plugin-gcc

3./usr/local/下必须新建文件夹mysql

mkdir /usr/local/mysql

3.编译安装

1.在源码目录/opt/mysql-8.0.39下执行cmake命令,进行编译前的检查和配置,使用CMake配置和生成MySQL源码编译所需的构建系统文件,安装MySQL到/usr/local/mysql目录下,-DWITH_BOOST=的路径根据源码包内自带boost的版本而定,默认端口3306,数据路径/usr/local/mysql/data,字符集utf8mb4

cmake \-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \-DINSTALL_DATADIR=/usr/local/mysql/data \-DMYSQL_USER=mysql \-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysqld.sock \-DDEFAULT_CHARSET=utf8mb4 \-DDEFAULT_COLLATION=utf8mb4_general_ci \-DWITH_INNOBASE_STORAGE_ENGINE=1 \-DWITH_EMBEDDED_SERVER=1 \-DFORCE_INSOURCE_BUILD=1 \-DWITH_MYISAM_STORAGE_ENGINE=1 \-DENABLED_LOCAL_INFILE=1 \-DEXTRA_CHARSETS=all \-DWITH_BOOST=boost/boost_1_77_0/

2.cmake命令执行成功后,执行以下make命令开始编译安装,-j 2是同时开启两个线程编译,加速编译

make -j 2 && make install

执行后,开始编译,不断输出日志,编译的过程很耗时间,需要等待很久,编译完成后,可以在/usr/local/mysql目录下看到编译好的MySQL

[root@localhost mysql-8.0.39]# cd /usr/local/mysql/[root@localhost mysql]# lltotal 704-rw-r--r--.  1 root root 282186 Jul 13 03:15 LICENSE-rw-r--r--.  1 root root 282186 Jul 13 03:15 LICENSE-test-rw-r--r--.  1 root root 115948 Jul 13 03:15 LICENSE.router-rw-r--r--.  1 root root    666 Jul 13 03:15 README-rw-r--r--.  1 root root    666 Jul 13 03:15 README-test-rw-r--r--.  1 root root    679 Jul 13 03:15 README.routerdrwxr-xr-x.  2 root root   4096 Dec 21 18:08 bindrwxr-xr-x.  2 root root     69 Dec 21 18:08 docsdrwxr-xr-x.  3 root root   4096 Dec 21 18:08 includedrwxr-xr-x.  6 root root   4096 Dec 21 18:08 libdrwxr-xr-x.  4 root root     30 Dec 21 18:08 mandrwxr-xr-x. 10 root root   4096 Dec 21 18:08 mysql-test-rw-r--r--.  1 root root   1672 Dec 21 13:33 mysqlrouter-log-rotatedrwxrwxr-x.  2 root root      6 Dec 21 18:08 rundrwxr-xr-x. 28 root root   4096 Dec 21 18:08 sharedrwxr-xr-x.  2 root root     77 Dec 21 18:08 support-filesdrwxr-xr-x.  3 root root     17 Dec 21 18:08 var

4.安装后配置

1.在/usr/local/mysql创建一个mysql配置文件,并写入字符集相关配置进去。

vi /usr/local/mysql/my.cnf
[mysqld]port = 3306character-set-server=utf8mb4default_authentication_plugin=mysql_native_password[mysql]default-character-set=utf8mb4

2.在/usr/local/mysql下执行命令初始化MySQL数据库

./bin/mysqld  \--defaults-file=/usr/local/mysql/my.cnf  \--user=mysql \--basedir=/usr/local/mysql  \--datadir=/usr/local/mysql/data  \--initialize-insecure

3.在/usr/local/mysql目录执行以下命令,将文件所有者设置为之前新建的mysql用户

chown -R mysql:mysql ./

5.启动和测试MySQL Server

/usr/local/mysql下执行启动脚本文件启动数据库

./support-files/mysql.server start

关闭

./support-files/mysql.server stop

重启

./support-files/mysql.server restart

登录数据库测试,切换到目录/usr/local/mysql,执行./bin/mysql -uroot -p命令登录数据库,root密码默认是空

[root@localhost mysql]# ./bin/mysql -uroot -pEnter password: Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 8Server version: 8.0.39 Source distributionCopyright (c) 2000, 2024, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;+--------------------+| Database           |+--------------------+| information_schema || mysql              || performance_schema || sys                |+--------------------+4 rows in set (0.01 sec)

修改默认密码

mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'lzj';

修改后要刷新权限

mysql> FLUSH PRIVILEGES;

刷新权限后退出使用新密码再次登录,此时已经可以正常操作数据库了。

[root@localhost mysql]# ./bin/mysql -uroot -pEnter password: Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 9Server version: 8.0.39 Source distributionCopyright (c) 2000, 2024, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;+--------------------+| Database           |+--------------------+| information_schema || mysql              || performance_schema || sys                |+--------------------+4 rows in set (0.01 sec)mysql> select version();+-----------+| version() |+-----------+| 8.0.39    |+-----------+1 row in set (0.00 sec)mysql> SHOW VARIABLES LIKE 'character_set%';+--------------------------+----------------------------------+| Variable_name            | Value                            |+--------------------------+----------------------------------+| character_set_client     | utf8mb4                          || character_set_connection | utf8mb4                          || character_set_database   | utf8mb4                          || character_set_filesystem | binary                           || character_set_results    | utf8mb4                          || character_set_server     | utf8mb4                          || character_set_system     | utf8mb3                          || character_sets_dir       | /usr/local/mysql/share/charsets/ |+--------------------------+----------------------------------+8 rows in set (0.02 sec)

至此,就完成了在RockyLinux9上面安装和运行MySQL8

  •  

MySQL字符集及底层原理

1.字符集优先级

查看MySQL中的字符集:

mysql> show variables like '%char%';+--------------------------+----------------------------+| Variable_name            | Value                      |+--------------------------+----------------------------+| character_set_client     | utf8                       || character_set_connection | utf8                       || character_set_database   | latin1                     || character_set_filesystem | binary                     || character_set_results    | utf8                       || character_set_server     | latin1                     || character_set_system     | utf8                       || character_sets_dir       | /usr/share/mysql/charsets/ |+--------------------------+----------------------------+8 rows in set (0.00 sec)
  • character_set_server: 服务器级别的字符集

  • character_set_database: 当前数据库的字符集

  • character_set_client: 服务器解码请求时使用的字符集

  • character_set_connection: 服务器处理请求时会把请求字符串从character_set_client转为character_set_connection

  • character_set_results: 服务器向客户端返回数据时使用的字符集

MySQL有4个级别的字符集和比较规则,分别是

  1. 服务器级别: character_set_server,配置在my.cnf配置文件character-set-server的字符集
  2. 数据库级别: character_set_database,character_set_server决定character_set_database
  3. 表级别: 在某个库下建表,如果没有指定表的字符集,默认与当前库的字符集一致
  4. 列级别: 表中的字段不指定字符集的时候,默认采用所在表的字符集。

2.utf8mb3和utf8mb4

查看MySQL支持的字符集(Charset)和对应的比较规则(Default collation)以及占用大小(Maxlen):

mysql> show charset;+----------+---------------------------------+---------------------+--------+| Charset  | Description                     | Default collation   | Maxlen |+----------+---------------------------------+---------------------+--------+| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 || dec8     | DEC West European               | dec8_swedish_ci     |      1 || cp850    | DOS West European               | cp850_general_ci    |      1 || hp8      | HP West European                | hp8_english_ci      |      1 || koi8r    | KOI8-R Relcom Russian           | koi8r_general_ci    |      1 || latin1   | cp1252 West European            | latin1_swedish_ci   |      1 || latin2   | ISO 8859-2 Central European     | latin2_general_ci   |      1 || swe7     | 7bit Swedish                    | swe7_swedish_ci     |      1 || ascii    | US ASCII                        | ascii_general_ci    |      1 || ujis     | EUC-JP Japanese                 | ujis_japanese_ci    |      3 || sjis     | Shift-JIS Japanese              | sjis_japanese_ci    |      2 || hebrew   | ISO 8859-8 Hebrew               | hebrew_general_ci   |      1 || tis620   | TIS620 Thai                     | tis620_thai_ci      |      1 || euckr    | EUC-KR Korean                   | euckr_korean_ci     |      2 || koi8u    | KOI8-U Ukrainian                | koi8u_general_ci    |      1 || gb2312   | GB2312 Simplified Chinese       | gb2312_chinese_ci   |      2 || greek    | ISO 8859-7 Greek                | greek_general_ci    |      1 || cp1250   | Windows Central European        | cp1250_general_ci   |      1 || gbk      | GBK Simplified Chinese          | gbk_chinese_ci      |      2 || latin5   | ISO 8859-9 Turkish              | latin5_turkish_ci   |      1 || armscii8 | ARMSCII-8 Armenian              | armscii8_general_ci |      1 || utf8     | UTF-8 Unicode                   | utf8_general_ci     |      3 || ucs2     | UCS-2 Unicode                   | ucs2_general_ci     |      2 || cp866    | DOS Russian                     | cp866_general_ci    |      1 || keybcs2  | DOS Kamenicky Czech-Slovak      | keybcs2_general_ci  |      1 || macce    | Mac Central European            | macce_general_ci    |      1 || macroman | Mac West European               | macroman_general_ci |      1 || cp852    | DOS Central European            | cp852_general_ci    |      1 || latin7   | ISO 8859-13 Baltic              | latin7_general_ci   |      1 || utf8mb4  | UTF-8 Unicode                   | utf8mb4_general_ci  |      4 || cp1251   | Windows Cyrillic                | cp1251_general_ci   |      1 || utf16    | UTF-16 Unicode                  | utf16_general_ci    |      4 || utf16le  | UTF-16LE Unicode                | utf16le_general_ci  |      4 || cp1256   | Windows Arabic                  | cp1256_general_ci   |      1 || cp1257   | Windows Baltic                  | cp1257_general_ci   |      1 || utf32    | UTF-32 Unicode                  | utf32_general_ci    |      4 || binary   | Binary pseudo charset           | binary              |      1 || geostd8  | GEOSTD8 Georgian                | geostd8_general_ci  |      1 || cp932    | SJIS for Windows Japanese       | cp932_japanese_ci   |      2 || eucjpms  | UJIS for Windows Japanese       | eucjpms_japanese_ci |      3 || gb18030  | China National Standard GB18030 | gb18030_chinese_ci  |      4 |+----------+---------------------------------+---------------------+--------+41 rows in set (0.00 sec)

utf8字符集表示一个字符需要使用1-4个字节,但是我们常用的一些字符使用1-3个字节就可以表示了。而字符集表示一个字符所用的最大字节长度,在某些方面会影响系统的存储和性能,所以设计MySOL的设计者自定义了两个概念

  • utf8mb3 阉割过的utf8字符集,只使用1-3个字节表示字符。
  • utf8mb4 正宗的utf8字符集,使用1-4个字节表示字符。

在MySQL中utf8是utf8mb3的别名,所以之后在MySQL中提到utf8就意味着使用1-3个字节来表示一个字符。如果有使用4字节编码一个字符的情况,比如存储一些emoii表情,那请使用utf8mb4。

3.比较规则

show charset;表中,一共支持41种字符集,其中的Default collation列表示这种字符集默认的比较规则,里面包含着该比较规则主要作用于哪种语言,比如utf8_polish_ci表示以波兰语的规则比较,utf8_spanish_ci是以西班牙语的规则比较,utf8_general_ci是一种通用的比较规则。

后缀表示该比较规则是否区分语言中的重音、大小写,具体如下:

后缀英文释义描述
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase sensitive区分大小写
_binbinary以二进制方式比较

查看某种字符集的比较规则

mysql> SHOW COLLATION LIKE'utf8%';+--------------------------+---------+-----+---------+----------+---------+| Collation                | Charset | Id  | Default | Compiled | Sortlen |+--------------------------+---------+-----+---------+----------+---------+| utf8_general_ci          | utf8    |  33 | Yes     | Yes      |       1 || utf8_bin                 | utf8    |  83 |         | Yes      |       1 || utf8_unicode_ci          | utf8    | 192 |         | Yes      |       8 || utf8_icelandic_ci        | utf8    | 193 |         | Yes      |       8 || utf8_latvian_ci          | utf8    | 194 |         | Yes      |       8 || utf8_romanian_ci         | utf8    | 195 |         | Yes      |       8 || utf8_slovenian_ci        | utf8    | 196 |         | Yes      |       8 || utf8_polish_ci           | utf8    | 197 |         | Yes      |       8 || utf8_estonian_ci         | utf8    | 198 |         | Yes      |       8 || utf8_spanish_ci          | utf8    | 199 |         | Yes      |       8 || utf8_swedish_ci          | utf8    | 200 |         | Yes      |       8 || utf8_turkish_ci          | utf8    | 201 |         | Yes      |       8 || utf8_czech_ci            | utf8    | 202 |         | Yes      |       8 || utf8_danish_ci           | utf8    | 203 |         | Yes      |       8 || utf8_lithuanian_ci       | utf8    | 204 |         | Yes      |       8 || utf8_slovak_ci           | utf8    | 205 |         | Yes      |       8 || utf8_spanish2_ci         | utf8    | 206 |         | Yes      |       8 || utf8_roman_ci            | utf8    | 207 |         | Yes      |       8 || utf8_persian_ci          | utf8    | 208 |         | Yes      |       8 || utf8_esperanto_ci        | utf8    | 209 |         | Yes      |       8 || utf8_hungarian_ci        | utf8    | 210 |         | Yes      |       8 || utf8_sinhala_ci          | utf8    | 211 |         | Yes      |       8 || utf8_german2_ci          | utf8    | 212 |         | Yes      |       8 || utf8_croatian_ci         | utf8    | 213 |         | Yes      |       8 || utf8_unicode_520_ci      | utf8    | 214 |         | Yes      |       8 || utf8_vietnamese_ci       | utf8    | 215 |         | Yes      |       8 || utf8_general_mysql500_ci | utf8    | 223 |         | Yes      |       1 || utf8mb4_general_ci       | utf8mb4 |  45 | Yes     | Yes      |       1 || utf8mb4_bin              | utf8mb4 |  46 |         | Yes      |       1 || utf8mb4_unicode_ci       | utf8mb4 | 224 |         | Yes      |       8 || utf8mb4_icelandic_ci     | utf8mb4 | 225 |         | Yes      |       8 || utf8mb4_latvian_ci       | utf8mb4 | 226 |         | Yes      |       8 || utf8mb4_romanian_ci      | utf8mb4 | 227 |         | Yes      |       8 || utf8mb4_slovenian_ci     | utf8mb4 | 228 |         | Yes      |       8 || utf8mb4_polish_ci        | utf8mb4 | 229 |         | Yes      |       8 || utf8mb4_estonian_ci      | utf8mb4 | 230 |         | Yes      |       8 || utf8mb4_spanish_ci       | utf8mb4 | 231 |         | Yes      |       8 || utf8mb4_swedish_ci       | utf8mb4 | 232 |         | Yes      |       8 || utf8mb4_turkish_ci       | utf8mb4 | 233 |         | Yes      |       8 || utf8mb4_czech_ci         | utf8mb4 | 234 |         | Yes      |       8 || utf8mb4_danish_ci        | utf8mb4 | 235 |         | Yes      |       8 || utf8mb4_lithuanian_ci    | utf8mb4 | 236 |         | Yes      |       8 || utf8mb4_slovak_ci        | utf8mb4 | 237 |         | Yes      |       8 || utf8mb4_spanish2_ci      | utf8mb4 | 238 |         | Yes      |       8 || utf8mb4_roman_ci         | utf8mb4 | 239 |         | Yes      |       8 || utf8mb4_persian_ci       | utf8mb4 | 240 |         | Yes      |       8 || utf8mb4_esperanto_ci     | utf8mb4 | 241 |         | Yes      |       8 || utf8mb4_hungarian_ci     | utf8mb4 | 242 |         | Yes      |       8 || utf8mb4_sinhala_ci       | utf8mb4 | 243 |         | Yes      |       8 || utf8mb4_german2_ci       | utf8mb4 | 244 |         | Yes      |       8 || utf8mb4_croatian_ci      | utf8mb4 | 245 |         | Yes      |       8 || utf8mb4_unicode_520_ci   | utf8mb4 | 246 |         | Yes      |       8 || utf8mb4_vietnamese_ci    | utf8mb4 | 247 |         | Yes      |       8 |+--------------------------+---------+-----+---------+----------+---------+53 rows in set (0.00 sec)

查看服务器的字符集和比较规则

mysql> SHOW VARIABLES LIKE '%_server';+----------------------+-------------------+| Variable_name        | Value             |+----------------------+-------------------+| character_set_server | latin1            || collation_server     | latin1_swedish_ci |+----------------------+-------------------+2 rows in set (0.02 sec)

查看当前数据库的字符集和比较规则

mysql> SHOW VARIABLES LIKE '%_database';+------------------------+-------------------+| Variable_name          | Value             |+------------------------+-------------------+| character_set_database | latin1            || collation_database     | latin1_swedish_ci || skip_show_database     | OFF               |+------------------------+-------------------+3 rows in set (0.00 sec)

修改具体数据库的字符集

ALTER DATABASE dbtest DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

utf8_unicode_ciutf8_general_ci对中、英文来说没有实质的差别。utf8_general_ci校对速度快,但准确度稍差。utf8_unicode_ci准确度高,但校对速度稍慢。

⚠️ 一般情况,用utf8_general_ci就够了,但如果你的应用有德语、法语或者俄语,请一定使用utf8_unicode_ci
⚠️ 修改了数据库的默认字符集和比较规则后,原来已经创建的表格的字符集和比较规则并不会改变,如果需要,那么需单独修改。

  •  

MySQL5.7x 主从复制

在MySQL中,主从复制(Master-Slave Replication)是一种常用的数据库复制技术,用于将主服务器(Master)的数据实时复制到一个或多个从服务器(Slave)。这有助于负载均衡、备份和高可用性。

前提条件

  • 主服务器从服务器的MySQL版本应兼容(最好相同版本)。
  • 所有服务器时钟应该同步,可以使用NTP确保时间一致性。
  • 从服务器初始时的数据要与主服务器一致(可以通过备份恢复的方式实现)。

一、配置主服务器(Master)

  1. 编辑主服务器的MySQL配置文件
    打开MySQL主服务器的配置文件 my.cnf 。

    添加或修改以下内容:

    [mysqld]server-id = 1                # 唯一的服务器ID,任意正整数,但需要在集群中唯一log-bin = mysql-bin           # 启用二进制日志,确保日志记录所有数据变更binlog-do-db = my_database    # 要复制的数据库(可以指定多个,也可以省略)
    • server-id:每个服务器都需要有唯一的server-id
    • log-bin:启用二进制日志,主从复制就是基于二进制日志的。
    • binlog-do-db:指定要复制的数据库,可以指定多个数据库。省略这行会复制所有数据库。

    重启MySQL服务:

    sudo systemctl restart mysqld
  2. 创建用于复制的用户
    在主服务器上登录MySQL并创建一个用于复制的用户。

    CREATE USER 'slave'@'%' IDENTIFIED BY 'password';GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';FLUSH PRIVILEGES;

    这里创建了一个名为slave的用户,密码为password,并授予了复制权限。

  3. 获取主服务器的状态信息
    在主服务器上运行以下命令,记录输出的信息:

    SHOW MASTER STATUS;

    输出类似以下内容:

    +------------------+----------+--------------+------------------+| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |+------------------+----------+--------------+------------------+| mysql-bin.000001 | 12345    | my_database  |                  |+------------------+----------+--------------+------------------+
    • File:二进制日志的文件名。
    • Position:当前日志的偏移量。这两个值将在配置从服务器时使用。

二、配置从服务器(Slave)

  1. 编辑从服务器的MySQL配置文件

    打开从服务器的配置文件my.cnf,添加或修改如下内容:

    [mysqld]server-id = 2             # 唯一的服务器ID,与主服务器不同relay-log = mysql-relay   # 中继日志,用于存储从主服务器接收到的日志

    重启从服务器的MySQL服务:

    sudo systemctl restart mysqld
  2. 初始化从服务器

    登录从服务器的MySQL,并执行以下步骤来设置复制:

    CHANGE MASTER TO    MASTER_HOST='master_ip_address',    MASTER_USER='replicator',    MASTER_PASSWORD='password',    MASTER_LOG_FILE='mysql-bin.000001',  -- 从SHOW MASTER STATUS获取的日志文件名    MASTER_LOG_POS=12345;                -- 从SHOW MASTER STATUS获取的偏移量

    在这里,master_ip_address是主服务器的IP地址,MASTER_LOG_FILEMASTER_LOG_POS是前面SHOW MASTER STATUS命令中看到的值。

  3. 启动复制

    在从服务器上启动复制进程:

    START SLAVE;
  4. 检查复制状态

    检查从服务器的复制状态,确保复制正常运行:

    SHOW SLAVE STATUS\G;

    查看输出中的两个关键字段:

    • Slave_IO_Running: Yes
    • Slave_SQL_Running: Yes

    如果这两个值都是Yes,说明主从复制正常。如果不是,可以检查错误信息并进行排查。

三、测试主从复制

  1. 在主服务器上创建一个测试表或插入数据:

    USE my_database;CREATE TABLE test_table (id INT PRIMARY KEY, data VARCHAR(50));INSERT INTO test_table VALUES (1, 'Test Data');
  2. 在从服务器上检查该表是否存在以及数据是否同步:

    USE my_database;SELECT * FROM test_table;

    如果能看到相同的表和数据,说明主从复制配置成功。

  •  

RockyLinux9环境下编译MySQL5.7

Linux版本: Rocky Linux release 9.5 (Blue Onyx)

1.下载

打开MySQL-Community-Server官方下载页面:https://downloads.mysql.com/archives/community/

筛选出要下载的版本,Product Version选择5.7.44,也就是最后一个小版本,Operating System选择Source Code,OS Version选择All Operating Systems,然后选择最后一个自带boost的MySQL源码包(mysql-boost-5.7.44.tar.gz),进行下载,并将下载到的源码包先放到服务器的/opt目录下。

解压tar包,得到文件夹mysql-5.7.44,里面就是MySQL源码和一些构建信息

[root@localhost opt]# cd mysql-5.7.44/[root@localhost mysql-5.7.44]# lltotal 528drwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 BUILD-rw-r--r--.  1 7161 31415  29939 Oct 11  2023 CMakeLists.txtdrwxr-xr-x.  2 7161 31415     98 Oct 11  2023 Docs-rw-r--r--.  1 7161 31415  67369 Oct 11  2023 Doxyfile-perfschema-rw-r--r--.  1 7161 31415    333 Oct 11  2023 INSTALL-rw-r--r--.  1 7161 31415 260678 Oct 11  2023 LICENSE-rw-r--r--.  1 7161 31415    566 Oct 11  2023 README-rw-r--r--.  1 7161 31415     88 Oct 11  2023 VERSIONdrwxr-xr-x.  3 7161 31415     26 Oct 11  2023 boostdrwxr-xr-x.  6 7161 31415   4096 Oct 11  2023 clientdrwxr-xr-x.  4 7161 31415   4096 Oct 11  2023 cmake-rw-r--r--.  1 7161 31415  14584 Oct 11  2023 config.h.cmake-rw-r--r--.  1 7161 31415  25217 Oct 11  2023 configure.cmakedrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 dbugdrwxr-xr-x.  8 7161 31415   4096 Oct 11  2023 extradrwxr-xr-x.  5 7161 31415   4096 Oct 11  2023 includedrwxr-xr-x.  5 7161 31415    135 Oct 11  2023 libbinlogeventsdrwxr-xr-x.  3 7161 31415     39 Oct 11  2023 libbinlogstandalonedrwxr-xr-x.  4 7161 31415   4096 Oct 11  2023 libmysqldrwxr-xr-x.  3 7161 31415   4096 Oct 11  2023 libmysqlddrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 libservicesdrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 mandrwxr-xr-x. 10 7161 31415   4096 Oct 11  2023 mysql-testdrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 mysysdrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 mysys_ssldrwxr-xr-x. 10 7161 31415    131 Oct 11  2023 packagingdrwxr-xr-x. 18 7161 31415   4096 Oct 11  2023 plugindrwxr-xr-x.  4 7161 31415     36 Oct 11  2023 rapiddrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 regexdrwxr-xr-x.  4 7161 31415   4096 Oct 11  2023 scriptsdrwxr-xr-x.  2 7161 31415      6 Oct 11  2023 source_downloadsdrwxr-xr-x.  8 7161 31415  20480 Oct 11  2023 sqldrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 sql-commondrwxr-xr-x. 13 7161 31415    175 Oct 11  2023 storagedrwxr-xr-x.  2 7161 31415   4096 Oct 11  2023 stringsdrwxr-xr-x.  4 7161 31415   4096 Oct 11  2023 support-filesdrwxr-xr-x.  2 7161 31415     98 Oct 11  2023 testclientsdrwxr-xr-x.  5 7161 31415     70 Oct 11  2023 unittestdrwxr-xr-x.  3 7161 31415   4096 Oct 11  2023 viodrwxr-xr-x.  2 7161 31415     32 Oct 11  2023 win

进入boost/文件夹下,可以看到目前版本依赖的boost版本是哪个,一会编译时会用到。

[root@localhost mysql-5.7.44]# cd boost/[root@localhost boost]# lltotal 0drwxr-xr-x. 3 7161 31415 19 Oct 11  2023 boost_1_59_0

2.编译安装前准备

1.新添加一个用户: mysql

useradd mysql

2.安装编译所需要的软件包依赖

yum -y install make gcc-c++ cmake bison  perl autoconf ncurses-devel openssl-devel libtirpc

在Rocky9上,有的依赖包没有自带,也无法通过yum安装,需要下载.rpm到本地,然后通过rpm -ivh安装

yum install -y wgetwget https://dl.rockylinux.org/pub/rocky/9/devel/x86_64/os/Packages/l/libtirpc-devel-1.3.3-9.el9.x86_64.rpmwget https://dl.rockylinux.org/pub/rocky/9/AppStream/x86_64/os/Packages/r/rpcgen-1.4-9.el9.x86_64.rpmrpm -ivh libtirpc-devel-1.3.3-9.el9.x86_64.rpmrpm -ivh rpcgen-1.4-9.el9.x86_64.rpm

3./usr/local/下必须新建文件夹mysql

mkdir /usr/local/mysql

3.编译安装

1.在源码目录/opt/mysql-5.7.44下执行cmake命令,进行编译前的检查和配置,使用CMake配置和生成MySQL源码编译所需的构建系统文件,安装MySQL到/usr/local/mysql目录下,-DWITH_BOOST=的路径根据源码包内自带boost的版本而定,默认端口3306,数据路径/usr/local/mysql/data,字符集utf8mb4

cmake \-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \-DMYSQL_DATADIR=/usr/local/mysql/data \-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \-DSYSCONFDIR=/usr/local/mysql \-DWITH_MYISAM_STORAGE_ENGINE=1 \-DWITH_ARCHIVE_STORAGE_ENGINE=1 \-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \-DWITH_INNOBASE_STORAGE_ENGINE=1 \-DWITH_MEMORY_STORAGE_ENGINE=1 \-DWITH_READLINE=1 \-DMYSQL_TCP_PORT=3306 \-DENABLED_LOCAL_INFILE=1 \-DWITH_PARTITION_STORAGE_ENGINE=1 \-DEXTRA_CHARSETS=all \-DDEFAULT_CHARSET=utf8mb4 \-DDEFAULT_COLLATION=utf8mb4_general_ci \-DWITH_BOOST=boost/boost_1_59_0/

2.cmake命令执行成功后,执行以下make命令开始编译安装

make && make install

执行后,开始编译,不断输出日志,编译的过程很耗时间,需要等待很久,编译完成后,可以在/usr/local/mysql目录下看到编译好的MySQL

[root@localhost mysql-5.7.44]# cd /usr/local/mysql/[root@localhost mysql]# lltotal 280-rw-r--r--.  1 root root 260678 Oct 11  2023 LICENSE-rw-r--r--.  1 root root    566 Oct 11  2023 README-rw-r--r--.  1 root root    566 Oct 11  2023 README-testdrwxr-xr-x.  2 root root   4096 Dec 15 20:12 bindrwxr-xr-x.  2 root root     73 Dec 15 20:12 docsdrwxr-xr-x.  3 root root   4096 Dec 15 20:12 includedrwxr-xr-x.  4 root root    192 Dec 15 20:12 libdrwxr-xr-x.  4 root root     30 Dec 15 20:12 mandrwxr-xr-x. 10 root root   4096 Dec 15 20:12 mysql-testdrwxr-xr-x. 28 root root   4096 Dec 15 20:12 sharedrwxr-xr-x.  2 root root     90 Dec 15 20:12 support-files

4.安装后配置

1.在/usr/local/mysql创建一个mysql配置文件,并写入字符集相关配置进去。

vi /usr/local/mysql/my.cnf
[mysqld]# 设置字符集为 utf8mb4character-set-server = utf8mb4collation-server = utf8mb4_unicode_ci# 可选: 设置客户端和连接的字符集[client]default-character-set = utf8mb4[mysql]default-character-set = utf8mb4

2.在/usr/local/mysql下执行命令初始化MySQL数据库

./bin/mysqld --defaults-file=/usr/local/mysql/my.cnf  --initialize --user=mysql

命令执行完成后,会为我们生成一个临时的root密码TKwei-cPH7>?,初始化完成后,目录内会多出一个data文件夹

[root@localhost mysql]# ./bin/mysqld --defaults-file=/usr/local/mysql/my.cnf  --initialize --user=mysql2024-12-15T13:56:33.192253Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).2024-12-15T13:56:34.692706Z 0 [Warning] InnoDB: New log files created, LSN=457902024-12-15T13:56:34.976850Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.2024-12-15T13:56:35.074466Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 658fc67b-baec-11ef-9db1-000c29c17c8f.2024-12-15T13:56:35.079233Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.2024-12-15T13:56:35.873224Z 0 [Warning] A deprecated TLS version TLSv1 is enabled. Please use TLSv1.2 or higher.2024-12-15T13:56:35.873307Z 0 [Warning] A deprecated TLS version TLSv1.1 is enabled. Please use TLSv1.2 or higher.2024-12-15T13:56:35.882642Z 0 [Warning] CA certificate ca.pem is self signed.2024-12-15T13:56:36.037915Z 1 [Note] A temporary password is generated for root@localhost: TKwei-cPH7>?
[root@localhost mysql]# cd /usr/local/mysql/[root@localhost mysql]# lltotal 284-rw-r--r--.  1 root  root  260678 Oct 11  2023 LICENSE-rw-r--r--.  1 root  root     566 Oct 11  2023 README-rw-r--r--.  1 root  root     566 Oct 11  2023 README-testdrwxr-xr-x.  2 root  root    4096 Dec 15 20:12 bindrwxr-x---.  5 mysql mysql   4096 Dec 15 21:56 datadrwxr-xr-x.  2 root  root      73 Dec 15 20:12 docsdrwxr-xr-x.  3 root  root    4096 Dec 15 20:12 includedrwxr-xr-x.  4 root  root     192 Dec 15 20:12 libdrwxr-xr-x.  4 root  root      30 Dec 15 20:12 man-rw-r--r--.  1 root  root       0 Dec 15 21:56 my.cnfdrwxr-xr-x. 10 root  root    4096 Dec 15 20:12 mysql-testdrwxr-xr-x. 28 root  root    4096 Dec 15 20:12 sharedrwxr-xr-x.  2 root  root      90 Dec 15 20:12 support-files

3.在/usr/local/mysql目录执行以下命令,将文件所有者设置为之前新建的mysql用户

chown -R mysql:mysql ./
[root@localhost mysql]# lltotal 284-rw-r--r--.  1 mysql mysql 260678 Oct 11  2023 LICENSE-rw-r--r--.  1 mysql mysql    566 Oct 11  2023 README-rw-r--r--.  1 mysql mysql    566 Oct 11  2023 README-testdrwxr-xr-x.  2 mysql mysql   4096 Dec 15 20:12 bindrwxr-x---.  5 mysql mysql   4096 Dec 15 21:56 datadrwxr-xr-x.  2 mysql mysql     73 Dec 15 20:12 docsdrwxr-xr-x.  3 mysql mysql   4096 Dec 15 20:12 includedrwxr-xr-x.  4 mysql mysql    192 Dec 15 20:12 libdrwxr-xr-x.  4 mysql mysql     30 Dec 15 20:12 man-rw-r--r--.  1 mysql mysql      0 Dec 15 21:56 my.cnfdrwxr-xr-x. 10 mysql mysql   4096 Dec 15 20:12 mysql-testdrwxr-xr-x. 28 mysql mysql   4096 Dec 15 20:12 sharedrwxr-xr-x.  2 mysql mysql     90 Dec 15 20:12 support-files

5.启动和测试MySQL Server

/usr/local/mysql下执行启动脚本文件启动数据库

./support-files/mysql.server start

关闭

./support-files/mysql.server stop

重启

./support-files/mysql.server restart

登录数据库测试,切换到目录/usr/local/mysql,在执行./bin/mysql -uroot -p命令登录数据库操作时,系统提示我们需要修改默认的root密码

[root@localhost mysql]# ./bin/mysql -uroot -pEnter password: Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 5Server version: 5.7.44Copyright (c) 2000, 2023, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

修改默认密码

mysql> SET PASSWORD = PASSWORD('lzj');

修改后要刷新权限

mysql> FLUSH PRIVILEGES;

刷新权限后退出使用新密码再次登录,此时已经可以正常操作数据库了。

[root@localhost mysql]# ./bin/mysql -uroot -plzjmysql: [Warning] Using a password on the command line interface can be insecure.Welcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 7Server version: 5.7.44 Source distributionCopyright (c) 2000, 2023, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> show databases;+--------------------+| Database           |+--------------------+| information_schema || mysql              || performance_schema || sys                |+--------------------+4 rows in set (0.00 sec)mysql> select version();+-----------+| version() |+-----------+| 5.7.44    |+-----------+1 row in set (0.00 sec)mysql> SHOW VARIABLES LIKE 'character_set%';+--------------------------+----------------------------------+| Variable_name            | Value                            |+--------------------------+----------------------------------+| character_set_client     | utf8mb4                          || character_set_connection | utf8mb4                          || character_set_database   | utf8mb4                          || character_set_filesystem | binary                           || character_set_results    | utf8mb4                          || character_set_server     | utf8mb4                          || character_set_system     | utf8                             || character_sets_dir       | /usr/local/mysql/share/charsets/ |+--------------------------+----------------------------------+8 rows in set (0.01 sec)

至此,就完成了在RockyLinux9上面安装和运行MySQL5.7

  •  

MySQL数据定义语言

1.库操作

1.1 创建数据库

CREATE DATABASE book

1.2 创建数据库,如果存在

CREATE DATABASE IF NOT EXISTS book

1.3 切换到某个数据库

use book

1.4 更改库字符集

ALTER DATABASE book CHARACTER SET utf8ALTER DATABASE book CHARACTER SET gbk

1.5 删除数据库

DROP DATABASE bookDROP DATABASE IF EXISTS book

2.数据类型

2.1 整形

整形 TINYINT SMALLINT MEDIUMINT INT(INTEGER) BIGINT,对于整形,长度由范围决定,设置的长度用来补零,但必须在建立字段时搭配zerofill,一旦zerofill,默认为无符号,如果不设置无符号,默认是有符号,如果插入数值超出范围,系统会警告,默认插入临界值。

# 设置有无符号create table user( age int UNSIGNED , id int )INSERT INTO `user` VALUES (-9,9)

2.2 浮点数

浮点型小数 float(m,d) double(m,d)
定点型小数 DECIMAL(m,d) ,精度高,最大取值范围与double相同,m 整数部分和小数部分合起来的位数,d 小数点后保留位数,超出范围默认是临界值

2.3 字符型

短文本char VARCHAR,长文本text

CHAR(m),m指最大字符数, 固定长度字符,开的空间与长度有关 ,耗费空间,效率高,可以省略m, m默认是1。
VARCHAR(m) m指最大字符数, 可变长度的字符,开的空间与实际占用有关,节省空间,效率低,不能省略m。

2.4 枚举型

enum('c','b') 掺入的值不在枚举内,默认是空,不区分大小写

2.5 SET

一次可以插入多个值,不区分大小写

2.6 日期值

必须用单引号引起,每个类型作用范围不同

date 1000-01-01 - 9999-12-31
time 838:59:59
datetime 1000-01-01 - 9999-12-31
year 1901 - 2155
TIMESTAMP 1970 - 2038年某一时刻,受时区影响,更能反应实际日期,与MySQL版本和sqlmode关系很大

3.表操作

3.1 创建表

CREATE TABLE book(id INT(10) ,`name` VARCHAR (30),price DOUBLE,authorId INT(10),publishDate DATETIME);CREATE TABLE author(id INT,`name` VARCHAR(20));

3.2 查看表描述

DESC book

3.3 修改列名 类型

ALTER TABLE book CHANGE COLUMN publishDate pubDate datetime

3.4 修改类约束 类型

ALTER TABLE book MODIFY COLUMN pubDate datetime

3.5 添加新列

ALTER TABLE author ADD COLUMN last_name VARCHAR(20)ALTER TABLE author ADD COLUMN first_name VARCHAR(20)

3.6 删除列

ALTER TABLE author DROP COLUMN first_name

3.7 修改表名

ALTER TABLE author RENAME TO `authors`

3.8 删除表

DROP TABLE IF EXISTS `authors`

3.9 复制

仅仅复制结构

CREATE TABLE 1_author LIKE `author`

复制结构和数据

CREATE TABLE 12_author SELECT * FROM `author`

条件复制

CREATE TABLE 12_author SELECT * FROM `author` WHERE id = 1

复制部分列

CREATE TABLE 12_author SELECT `name` FROM `author` WHERE id = 1

复制部分列,不要数据,使用恒不成立

CREATE TABLE 12_author SELECT `name` FROM `author` WHERE 2 = 1CREATE TABLE 12_author SELECT `name` FROM `author` WHERE 0 /*0==false*/

4.约束

6种约束

  1. NOT NULL (非空约束): 确保列不能有 NULL 值。常用于必须填写的字段。
  2. DEFAULT (默认值约束): 如果插入数据时未指定该列的值,则使用默认值填充。
  3. PRIMARY KEY (主键约束): 保证字段值的唯一性和非空,常用于唯一标识表中每一行的字段。
  4. UNIQUE (唯一约束): 确保列中的值唯一,但可以有 NULL 值。
  5. CHECK (检查约束): 验证字段的值是否符合指定条件,但 MySQL 并不支持 CHECK 约束。
  6. FOREIGN KEY (外键约束): 设定两个表之间的关系,确保该字段的值必须来自于主表的关联列。

表级与列级约束:

  • 表级约束: 可以在创建表时定义,除 NOT NULLDEFAULT 之外的所有约束都支持。
  • 列级约束: 语法上支持所有约束,但 MySQL 中外键在列级定义时可能无法有效执行(需要在表级定义才能生效)。

4.1 主键

添加主键

ALTER TABLE stuinfo MODIFY COLUMN id INT PRIMARY KEY-- 支持表级约束的也可以这样ALTER TABLE stuinfo ADD PRIMARY KEY (id)
CREATE TABLE stuinfo(id int ,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT,CONSTRAINT pk/*mysql中,主键改名无效*/ PRIMARY KEY (id))

主键和唯一的对比

都可以保证唯一,主键不能为空,每个表只能有1个,可以两个列自合一起,唯一允许为空,一个表可以有多个,不能多个为空,可以两个列自合一起。

外键,从表设置外键关系,数据类型一致或兼容,主表被关联列必须是一个key,一般是主键或唯一,插入数据时,先插入主表在插入从表,删除数据,先删除从表,再删除主表。

4.2 非空

CREATE TABLE stuinfo(id int ,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT)-- 修改表时添加约束ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NOT NULLALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NULL

4.3 唯一

添加唯一

ALTER TABLE stuinfo ADD UNIQUE (seat)

4.4 外键

添加外键

ALTER TABLE stuinfo ADD FOREIGN KEY (majarId) REFERENCES majar(id)

添加外键 名字

ALTER TABLE stuinfo ADD CONSTRAINT fk_majar FOREIGN KEY (majarId) REFERENCES majar(id)

4.5删除约束

删除非空

ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NULL

不添加条件,就会删除默认约束

ALTER TABLE stuinfo MODIFY COLUMN age 

删除主键

ALTER TABLE stuinfo DROP PRIMARY KEY

先查到,在删除唯一约束

SHOW INDEX FROM stuinfo ALTER TABLE stuinfo DROP INDEX seat

删除外键

ALTER TABLE stuinfo DROP FOREIGN KEY fk_majar

4.6标识列

限制某个字段,又叫自增长列,默认从1开始,必须是一个key,同一张表标识列只能有一个,只能是数值类型

CREATE TABLE stuinfo(id int PRIMARY KEY auto_increment,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT)

步长auto_increment_increment 起始值auto_increment_offset,mysql 可以设置步长,但是不能设置起始值

show VARIABLES like '%auto_increment%'

设置删除标识列

ALTER TABLE stuinfo MODIFY COLUMN id int PRIMARY KEY auto_incrementALTER TABLE stuinfo MODIFY COLUMN id int  
  •  

MySQL存储过程

使用存储过程,减少编译次数和连接数据库次数,提高效率

1. 创建

参数模式

  • IN :可以作为输入,需要调用者传入值
  • OUT :可以作为输出,可以作为返回值
  • INOUT:既可以传入值,又可以返回值

语法

CREATE PROCEDURE 存储过程名(参数模式 参数名 参数类型,参数模式 参数名 参数类型,...) BEGIN (语句只有一条 begin end 可省略)合法有效的SQL语句;(分号)合法有效的SQL语句;END

示例

delimiter $CREATE PROCEDURE pr1()BEGININSERT INTO book(`name`, price, author) VALUES('012',23.56,'lzj');INSERT INTO book(`name`, price, author) VALUES('45',23.56,'lzj');INSERT INTO book(`name`, price, author) VALUES('12',23.56,'lzj');INSERT INTO book(`name`, price, author) VALUES('87',23.56,'lzj');INSERT INTO book(`name`, price, author) VALUES('32',23.56,'lzj');END $

调用示例

CALL name(var, var);CALL pr1();

2. IN

创建

delimiter $CREATE procedure pr2(IN bid INT)BEGINSELECT * FROM beauty WHERE boyfriend_id = bid;END $delimiter $CREATE PROCEDURE pr3(IN username VARCHAR(20), IN `password` VARCHAR(20))BEGINDECLARE res INT;SELECT COUNT(*) INTO res FROM admin WHERE admin.username = username AND admin.`password` = `password`;SELECT res;END $

调用

CALL pr2(12);CALL pr3('john','8888');

3. OUT

创建

delimiter $CREATE PROCEDURE pr4(IN girl INT, OUT boy INT)BEGINSELECT beauty.boyfriend_id INTO boy FROM beauty WHERE beauty.id = girl;END $

调用

CALL pr4(1, @boy);SELECT @boy;

4. 两个OUT的存储过程

创建

delimiter $CREATE PROCEDURE pr5(IN girlid INT, OUT boyid INT, OUT girlname VARCHAR(20))BEGINSELECT beauty.boyfriend_id,  beauty.`name` INTO boyid, girlnameFROM beauty WHERE beauty.id = girlid;END $

调用

CALL pr5(2, @boyid, @girlname);SELECT  @boyid;SELECT @girlname;

5. INOUT

创建

delimiter $CREATE PROCEDURE pr6(INOUT a INT, INOUT b INT) BEGINSET a = a*2;SET b = b*2;END $

调用

SET @i = 2;SET @j = 4;call pr6(@i, @j);SELECT @i;SELECT @j;

6. 删除

只能同时删除一个

DROP PROCEDURE pr2;

7. 查看

show CREATE PROCEDURE pr3;
  •  

MySQL变量

  • 系统变量:

    • 全局变量:整个服务器有效
    • 会话变量:一次连接
  • 自定义变量:

    • 用户变量
    • 局部变量

查看所有会话变量

SHOW VARIABLES ;

所有全局变量

SHOW GLOBAL VARIABLES ;

查看部分全局变量的值

SHOW GLOBAL VARIABLES LIKE '%char%';SHOW SESSION VARIABLES ;SHOW SESSION VARIABLES LIKE '%auto%'SHOW GLOBAL VARIABLES LIKE '%auto%'

查看系统变量名

-- 默认查会话变量SELECT  @@varname ;SELECT  @@session.varname ;-- 查询系统变量SELECT @@globle.varname ;SELECT @@auto_increment_increment ;

为变量赋值

SET GLOBAL (如果是SESSION可以不写) varname = valueSET @@global varname = valueSET @@session varname = value

自定义变量

用户变量针对当前连接有效,声明时必须初始化,可以不指定类型

三种语法

SET @mycat = 'helloworld';SET @car := 'car';SELECT @cat := 'cat';

更新

适用于声明的语法,还可以查出一个值赋给变量

SELECT employees.email INTO @cat FROM employees WHERE employee_id = 100

查询变量的值

SELECT @cat

使用用户变量也需要@

SET @a = 1;SET @b = 2;SET @c = @a + @b;SELECT @c
  •  

MySQL视图

视图,即基于查询的虚拟表,始于mysql 5.1 ,通过普通表动态生成的数据,适用于复杂查询,在使用时动态生成,只保存了SQL逻辑,适用于多个地方用到同一个SQL,且SQL复杂,可以隐藏列定义,保护数据,不会暴漏原始表。

1.创建

CREATE VIEW empjobdept AS SELECT employees.last_name AS lastname,employees.employee_id AS eid,departments.department_name AS deptname,jobs.job_title AS jobtitleFROM employees LEFT JOIN departments ON departments.department_id = employees.department_idLEFT JOIN jobs ON jobs.job_id = employees.job_id

2.使用视图

SELECT * from empjobdept

3.修改视图

存在就修改,不存在就创建

CREATE OR REPLACE VIEW AS 查询语句ALTER VIEW 视图名 AS 查询语句

4.删除视图

DROP VIEW 视图名,视图名....

5.查看视图

DESC empjobdept;show create view empjobdept;

6.修改数据

视图的数据支持更新修改和删除,而且会影响到原始表。

不能编辑视图的情况

1.包含分组函数, GROUP BYHAVING ,去重, 联合, 子查询等语句。
2.from不能更改的视图。
3.常量视图。
4.WHERE字句子查询引用了from字句中的表时。

7.视图和表的对比

创建语法不同,视图数据不占用物理空间,只保存逻辑。

  •  

MySQL事务

1.事务

MySQL的事务是一组被视为一个单一逻辑工作单元的SQL操作。事务确保这些操作要么全部成功,要么在出现错误时全部失败并回滚,以保持数据库的一致性和完整性。事务通常用于需要多个步骤的操作,这些步骤必须全部成功才能确保数据的完整性,比如在银行转账中,转出和转入账户的操作必须同时完成。

在MySQL中,不同的存储引擎具有不同的特性,myisam memory存储引擎均不支持事务,如果你需要事务支持,InnoDB 是最推荐的选择,因为它在性能和事务处理方面表现优异,并且是MySQL的默认存储引擎。

查看当前存储引擎

SHOW ENGINES;

查看事务是否自动提交

SHOW VARIABLES LIKE '%autocommit%';

MySQL隐式事务INSERT, UPDATE, DELETE默认开启自动提交

2.事务的特性

  • Atomicity, 原子性:每个事务都是不可分割的单位,要么全部执行,要么全部失败
  • Consistency, 一致性:使数据库从一个一致的状态切换到另一个一致性的状态
  • Isolation, 隔离性:一个事务执行不受其他事务干扰(需要隔离级别控制)
  • Durability, 持久性:一个事务,一旦提交,就是永久性改变

3.事务的语法

提交一个事务

-- 必须,使用多个语句的事务,需要禁用隐式事务SET autocommit = 0;  -- 可选START TRANSACTION; -- 只有增删改查可以有事务,ddl语句没有事务UPDATE account SET money = money-20 WHERE id = 1; UPDATE account SET money = money+20 WHERE id = 2; -- 结束事务,应用程序外无法决定使用提交还是回滚,COMMIT,ROLLBACK只能手动选择一个COMMIT;-- ROLLBACK;

回滚到回滚点

-- 必须,使用多个语句的事务,需要禁用隐式事务SET autocommit = 0;   -- 可选START TRANSACTION; -- 只有增删改查可以有事务,ddl语句没有事务UPDATE account SET money = money-20 WHERE id = 1; -- 回滚点SAVEPOINT aUPDATE account SET money = money+20 WHERE id = 2; -- 结束事务,应用程序外无法决定使用提交还是回滚,COMMIT,ROLLBACK只能手动选择一个-- COMMIT;ROLLBACK TO a;

4.事务的隔离级别

运行多个事务,访问相同数据,如果不采取隔离机制,就会引发并发问题,设置隔离级别避免并发问题,Oracle支持:读已提交,串行化,默认读已提交,MySQL支持:读未提交 ,读已提交 ,可重复读 ,串行化,默认可重复读。

4.1 事务中的读现象

  1. 脏读

    • 定义:一个事务读取到另一个未提交事务修改的数据,如果该事务回滚,那么读取的数据将是无效的。
    • 影响:导致数据不一致,通常与更新操作相关。
  2. 不可重复读

    • 定义:一个事务内多次读取同一数据,结果却不一致,这是因为在两次读取之间,其他事务修改了数据。
    • 影响:导致一个事务在不同时间点读取相同数据的结果不同,通常与更新操作相关。
  3. 幻读

    • 定义:一个事务读取到的结果和之后的读取不一致,是因为其他事务插入或删除了数据行。
    • 影响:事务在读取同一范围的数据时,发现新插入或删除的“幻影”行,通常与插入或删除操作相关。

4.2 事务的隔离级别及其特性

  1. 读未提交(Read Uncommitted)

    • 允许现象:脏读。
    • 特点:事务可以读取到其他事务未提交的修改。
    • 问题:数据一致性最低。
  2. 读已提交(Read Committed)

    • 避免:脏读。
    • 允许现象:不可重复读和幻读。
    • 特点:一个事务只能读取其他已提交事务的数据。
    • 问题:数据在同一事务内多次读取时,结果可能不一致。
  3. 可重复读(Repeatable Read)

    • 避免:脏读和不可重复读。
    • 允许现象:幻读。
    • 特点:保证一个事务内多次读取同一数据时结果一致。
    • 问题:数据一致性更高,但仍可能出现幻读。
  4. 串行化(Serializable)

    • 避免:脏读、不可重复读、幻读。
    • 特点:事务按顺序执行,完全隔离,达到最高级别的数据一致性。
    • 问题:性能最低,因事务需要排队,常引起锁竞争。

总结

  • 脏读可以通过将隔离级别提高到 读已提交 或更高来避免。
  • 不可重复读可通过使用 可重复读串行化 隔离级别来避免。
  • 幻读只有在 串行化 隔离级别下才完全解决,但这会牺牲性能。

4.3设置隔离级别

查看当前隔离级别

SELECT @@tx_isolation

设置隔离级别为读未提交

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  •  

MySQL插入修改和删除

插入

支持一次性插入多行,支持子查询

INSERT INTO beauty(id,`name`,sex,borndate,phone,photo,boyfriend_id) VALUES (13, 'fbb', '女', '1997-01-01', '', NULL, 2);

多行

INSERT INTO beauty(id,`name`,sex,borndate,phone,photo,boyfriend_id) VALUES (17, '杨紫', '女', '1997-01-01', '', NULL, 2),(18, '蒋欣', '女', '1997-01-01', '', NULL, 2);

不支持一次插入多行

INSERT INTO beauty SET id = 14,`name` = '刘涛',phone = '135'

子查询

INSERT INTO beauty(id, `name`, phone)SELECT 26, "赵丽颖", "454" ;

修改

修改多表记录

UPDATE boys boyINNER JOIN beauty bea ON bea.boyfriend_id = boy.idSET bea.phone = "12344555"WHERE boy.id = 1UPDATE beauty LEFT JOIN boys ON boys.id = beauty.boyfriend_idSET beauty.phone = "cn666"WHERE beauty.boyfriend_id is NULL

删除

删除,有返回值,自增长从上个记录开始,支持事务回滚

DELETE FROM beauty WHERE id = 27

删谁delete后面就写谁

DELETE beautyFROM beauty INNER JOIN boys ON beauty.boyfriend_id = boys.idWHERE boys.id = 1DELETE beauty, boysFROM beauty INNER JOIN boys ON beauty.boyfriend_id = boys.idWHERE boys.id = 3

清空整个表,没有返回值,自增长从1开始,不支持事务回滚

TRUNCATE TABLE beautyy
  •  

MySQL查询

1.常量

SELECT 6;

2.字符常量

SELECT 'a';SELECT "aaa";

3.表达式

SELECT 100 * 98;

4.函数

SELECT VERSION();SELECT DATABASE();SELECT USER();

5.别名

SELECT 'A' AS B;SELECT 'A' B;SELECT last_name AS, last_name ASFROM employees;

别名有特殊字符,加双引号

SELECT last_name AS "SELECT #" FROM employees;

6.去重

查员工表涉及的部门编号,字段前加上 DISTINCT

SELECT DISTINCT department_id FROM employees;

7.拼接

加号的作用是数学运算不能连接字符串,两个操作数都为数值,则进行运算,其中一方为字符型,则试图将字符转换为数值,如果转换成功,继续运算,如果转换失败,则字符型的值转换为 0,只要其中一方为 NULL,结果为 NULL

SELECT NULL + 10;SELECT last_name + first_name AS 姓名FROM employees;SELECT CONCAT(last_name, first_name) AS 姓名 FROM employees AS 姓名;

拼接

SELECT CONCAT('a', 'b', 'c');

NULL 和任何值拼接,结果都是 NULL

SELECT CONCAT('a', 'b', NULL);

ifnull() 如果为空值

SELECT IFNULL(NULL, 'default') AS ifn;

8.条件查询

SELECT * FROM employees WHERE salary > 12000;SELECT * FROM employees WHERE department_id <> 90;SELECT * FROM employees WHERE salary >= 10000 AND salary <= 20000;SELECT * FROM employees WHERE department_id < 90 OR department_id > 110 OR salary > 15000;SELECT * FROM employees WHERE NOT(department_id >= 90 AND department_id <= 110) OR salary > 15000;

9.模糊查询

百分号代表通配符,任意字符,也包含 0 个字符,下划线 _ 代表任意单个字符,

SELECT * FROM employees WHERE last_name LIKE '%o%';

查询第三个字母是 n,第五个字母是 n

SELECT * FROM employees WHERE last_name LIKE '__n_l%';

查询第二个字符就是下划线的特殊情况,要进行转义或切换通配符,否则直接__%查询会把所有查出

SELECT * FROM employees WHERE last_name LIKE '_\_%';SELECT * FROM employees WHERE last_name LIKE '_$_%' ESCAPE '$';

10.范围查询

BETWEEN AND 可以提高简捷度,结果包含区间值,临界值的位置不能颠倒,否则零行

SELECT * FROM employees WHERE employee_id BETWEEN 100 AND 120;SELECT * FROM employees WHERE salary NOT BETWEEN 8000 AND 17000;

11.IN 查询

IN 值必须相同或兼容,不能使用通配符

SELECT * FROM employees WHERE job_id IN ('IT_PROG', 'AD_VP');

12.NULL 查询

查询 NULL 用 IS / IS NOT,等于和不等于号不能判断空值

SELECT * FROM employees WHERE commission_pct IS NULL;SELECT * FROM employees WHERE commission_pct IS NOT NULL;

安全等于可用于普通值也可以判断空值

SELECT * FROM employees WHERE commission_pct <=> NULL;

13.排序查询

升序, ASC 可以省略

SELECT * FROM employees ORDER BY salary ASC; 

降序

SELECT * FROM employees ORDER BY salary DESC;

条件排序

SELECT * FROM employees WHERE department_id >= 90 ORDER BY hiredate ASC;

表达式排序

SELECT   last_name,   first_name,   salary * 12 * (1 + IFNULL(commission_pct, 0)) AS annual_salary FROM   employeesORDER BY annual_salary ASC;

按照姓名长度

SELECT   LENGTH(CONCAT(last_name, first_name)) AS length,   last_name,   first_name,   salaryFROM   employeesORDER BY length;

多字段排序

SELECT * FROM employees ORDER BY salary ASC, employee_id DESC;

14.子查询

出现在其他语句中的查询语句就是子查询,也叫内查询。

子查询分为几种

  • 标量子查询:结果集一行一列
  • 列子查询:结果集一列多行
  • 行子查询:结果集一行多列
  • 表子查询:只要是查询结果就构成

子查询的使用规则

  1. 子查询必须放在小括号内
  2. 子查询通常放在条件的右侧,例如 WHEREHAVING 子句中的条件判断。
  3. 标量子查询:通常配合单行操作符(如 =<> 等)使用。
  4. 列子查询:通常配合多行操作符(如 INANYSOMEALL)使用。

子查询位置与支持情况

  • SELECT 后面:仅支持标量子查询。
  • FROM 后面:支持表子查询。
  • WHEREHAVING :支持标量子查询、列子查询,并且能支持行子查询。
  • EXISTS :后面支持表子查询,通常用于判断某个条件是否存在。

14.1标量子查询

工资大于107号员工的人

SELECT last_name FROM employees WHERE salary > (SELECT salary FROM employees WHERE employee_id = 107)

与141号相同工种,工资比143号大的人

SELECT employee_id, last_name, job_id, salary FROM employees WHERE job_id = (select job_id FROM employees WHERE employee_id = 141)AND salary > (SELECT salary FROM employees WHERE employee_id = 143)

哪个部门最低工资大于50号部门的最低工资

SELECT job_id, MIN(salary) AS min FROM employees GROUP BY job_idHAVING min > (SELECT MIN(salary) FROM employees WHERE department_id = 50)

工资最少

select * from employeesWHERE salary = (select MIN(salary) FROM employees )

14.2 列子查询

location_id 是1400,1700的部门的员工

SELECT last_name, department_id FROM employees WHERE department_id IN (SELECT DISTINCT department_id FROM departments WHERE location_id IN (1400,1700)) 

比job_id 是 ‘IT_PROG’的任意一个员工工资少的其他工种的员工

SELECT * FROM employees WHERE salary < ANY(SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG')AND job_id <> 'IT_PROG'

比job_id 是 ‘IT_PROG’的所有的员工工资都少的其他工种的员工

SELECT * FROM employees WHERE salary < ALL(SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG')AND job_id <> 'IT_PROG'

查询编号最小,工资最高的人 行子查询

SELECT * FROM employees WHERE (employee_id, salary) = (SELECT MIN(employee_id), MAX(salary) FROM employees)

14.3 SELECT 后的子查询

每个部门员工个数

SELECT dept.department_id AS deptId, (SELECT COUNT(*) FROM employees WHERE department_id = dept.department_id) AS countFROM departments dept

102号员工的部门名

SELECT employees.employee_id, employees.last_name,(SELECT department_name FROM departments WHERE department_id = employees.department_id) AS deptNameFROM employees WHERE employee_id = 102

from后的子查询

每个部门平均工资等级

SELECT jg.grade_level AS lavel ,avg_dep.salary AS salary,avg_dep.deptId AS deptIdFROM job_grades jgRIGHT JOIN (SELECT AVG(salary) AS salary, department_id AS deptId FROM employees GROUP BY department_id) avg_depON avg_dep.salary BETWEEN jg.lowest_sal AND highest_sal

14.4 EXISTS后子查询

相关子查询,布尔类型,看查询是否有值,括号内写查询语句

SELECT EXISTS(SELECT * FROM employees)SELECT EXISTS(SELECT * FROM employees WHERE salary = 251526)

查询有员工的部门

SELECT department_name FROM departments WHERE EXISTS (SELECT last_name, department_id FROM employees WHERE employees.department_id = departments.department_id)

没有配偶的男生

SELECT * FROM boys WHERE NOT EXISTS (SELECT * from beauty WHERE boyfriend_id = boys.id)

15.连接查询

连接查询的分类:

按年代

  • sql92标准
  • sql99标准(推荐)

按功能

  • 内连接 :等值连接 非等值连接 自连接
  • 外连接 :左外连接 右外连接 全外连接
  • 交叉连接

15.1 笛卡尔积

所有行全部连接,因缺少条件造成

select `name`,boyName from beauty, boys

15.2 等值连接sql92

员工名和部门名

SELECT last_name, department_name FROM departments, employeesWHERE employees.department_id = departments.department_id

有奖金的员工名,工种号和工种名

SELECT last_name, jobs.job_id, job_title FROM jobs, employeesWHERE employees.job_id = jobs.job_idAND employees.commission_pct is not null

每个城市的部门数量

SELECT COUNT(departments.department_id) , locations.city FROM locations, departments WHERE departments.location_id = locations.location_idGROUP BY locations.city

15.3 非等值连接sql92

SELECT salary, employee_id ,grade_level from employees, job_grades AS gradesWHERE salary BETWEEN grades.lowest_sal AND grades.highest_sal

15.4 自连接sql92

员工名和上级的名字

SELECT emp.last_name , emp.employee_id , emp.manager_id FROM employees emp , employees mangWHERE emp.manager_id = mang.employee_id

15.5 等值连接sql99

SELECT job_title, last_name FROM employees empINNER JOIN jobs job ON job.job_id = emp.job_id

INNER省略

SELECT job_title, last_name FROM employees emp JOIN jobs job ON job.job_id = emp.job_id

15.6 非等值连接sql99

SELECT salary, employee_id ,grade_level from employeesINNER JOIN job_grades AS grades ON salary BETWEEN grades.lowest_sal AND grades.highest_sal

15.7 外连接

外连接
查一个表有一个表没有,主表都显示,副表和主表匹配的显示出来,没有匹配的用空填充。

15.7.1左外连接

left join 左边是主表

select beauty.`name`, boys.boyName FROM beautyLEFT OUTER JOIN boys ON beauty.boyfriend_id = boys.id

15.7.2 右外连接

right join 右边的是主表

select beauty.`name`, boys.boyName FROM boysRIGHT OUTER JOIN beauty ON beauty.boyfriend_id = boys.id

没有员工的部门

select emp.last_name , dept.department_id FROM departments dept LEFT OUTER JOIN employees empON dept.department_id = emp.department_id WHERE emp.employee_id is NULL

15.7.3 交叉连接

笛卡尔积

SELECT beauty.*, boys.* FROM beautyCROSS JOIN boys 
  •  

MySQL系统命令

登录命令

mysql -h 主机 -P 端口 -u root -p   

密码与p之间不得有空格,其他项之间可以有

mysql -h 主机 -P 端口 -u root -p密码

数据库备份

mysqldump -h主机名 -P端口 -u用户名 -p密码 --database 数据库名 > 文件名.sql 

导入数据库

mysql -u用户名 -p密码 feaf_accounting < /tmp/accounting.sql

  •  

MySQL函数

MySQL函数分为两种: 单行函数:做处理 ,分组函数:做统计

1.单行函数

字节数

SELECT LENGTH('1234哈哈') AS len -- 字节数

查看客户端字符集

SHOW VARIABLES LIKE '%char%'

字符串连接 大写 小写

SELECT CONCAT('a','b','c') SELECT UPPER('ASddfDSU')SELECT LOWER('ASddfDSU')

索引从1开始,从某一位开始的截取 字符长度

SELECT SUBSTR('helloworld', 6)

从某一索引开始,截取某段长度 字符长度

SELECT SUBSTR('helloworld',1,3)SELECT CONCAT(  UPPER( SUBSTR(last_name, 1, 1) )    ) FROM employees

返回起始索引 如果找不到返回0

SELECT INSTR('helloworld','or')

去前后空格

SELECT TRIM('  vdfsv  scs  ')

去掉首尾的o

SELECT TRIM('o' FROM 'ooooooooooooooooooheoooolloooooooooooooooooooooooooooooooo')

指定字符左填充到指定长度,如果超过,右边的被截断

SELECT LPAD('hello',10,'*')SELECT LPAD('hellohellohtllohello',10,'*')

指定字符右填充到指定长度,如果超过,右边的被截断

SELECT RPAD('hello',10,'*')SELECT RPAD('hellohellohtllohello',10,'*')

替换

SELECT REPLACE('王老八夜里打酱油和酱油','酱油','酒')

数学函数

ROUND(x) 四舍五入

SELECT ROUND(1.65)

ROUND(x, y) 小数保留

SELECT ROUND(1.6545, 2)SELECT ROUND(1.4578, 2)

上取整,返回大于等于参数的最小整数

SELECT CEIL(1.025)SELECT CEIL(1.00)SELECT CEIL(-1.025)SELECT CEIL(-1.00)

下取整,返回小于等于参数的最大整数

SELECT FLOOR(1.021)SELECT FLOOR(9.6)SELECT FLOOR(-9.6)

截断

SELECT TRUNCATE(1.65,1)

%:余数

SELECT MOD(-11,3)SELECT MOD(11,3)

日期时间函数

SELECT NOW()SELECT CURRENT_TIME()SELECT YEAR(NOW())SELECT YEAR('1996-08-14')SELECT MONTH(NOW())SELECT MONTHNAME(NOW())

字符通过指定格式转换成日期

SELECT STR_TO_DATE('14-8-1996','%d-%m-%Y')SELECT STR_TO_DATE('14-8-96','%d-%m-%y')SELECT * FROM employees WHERE hiredate = '1992-4-3'SELECT * FROM employees WHERE hiredate = STR_TO_DATE('4-3 1992','%c-%d %Y')SELECT DATE_FORMAT(NOW(),'%y-%m-%d')SELECT DATE_FORMAT(NOW(),'%Y-%m-%d')SELECT DATE_FORMAT(NOW(),'%c-%d %Y')SELECT last_name, DATE_FORMAT(hiredate,'%y年/%m月 %d日') AS hiredate FROM employees

流程控制函数

SELECT IF(1=1,1,0)SELECT IF(1=41,1,0)SELECT last_name, commission_pct, IF(commission_pct IS NOT NULL, '有奖金', '没奖金') FROM employees ORDER BY commission_pct DESC/*case结构1 类似switch 适合判断等值运算*/SELECTlast_name,department_id,salary,CASEdepartment_id WHEN 30 THENsalary * 1.2 WHEN 40 THENsalary * 1.5 ELSE salary END AS new_salary FROMemployees/*case结构1 类似多重if 适合判断范围运算*/SELECTlast_name,department_id,salary,CASE WHEN salary > 20000 THEN 'A'WHEN salary > 15000 THEN 'B'WHEN salary > 10000 THEN 'C'ELSE'D'END AS 工资级别 FROM employees;

2.分组函数

分组函数用作统计,又叫组函数,聚合函数,统计函数

求和,忽略空值

SELECT SUM(salary) FROM employees;

平均,忽略空值

SELECT AVG(salary) FROM employees;

最大最小值,忽略空值

SELECT MIN(salary) FROM employeesSELECT MAX(salary) FROM employeesSELECT MAX(last_name), MIN(last_name) FROM employeesSELECT MAX(hiredate), MIN(hiredate) FROM employees

count函数
计算非空的值的个数,myisam下 COUNT()效率最高,INNODB下 COUNT() COUNT(1) 效率差不多,COUNT(字段)效率最低。

SELECT COUNT(employees.commission_pct) FROM employeesSELECT COUNT(employees.employee_id) FROM employees

可以用来查询总行数,某一列一个字段有值就统计上

SELECT COUNT(*) FROM employees

加上常量值,相当于表中添加一列,可以用来查询总行数

SELECT COUNT(1) FROM employees

DISTINCT 去重后统计

SELECT count(salary) FROM employeesSELECT count(DISTINCT salary) FROM employees

分组查询

和分组函数一同查询的字段要求是group by 后的字段

每个部门的平均工资

SELECT  AVG(salary) , department_id FROM employees GROUP BY department_id

工种最高工资

SELECT MAX(salary) salary, job_id FROM employees GROUP BY job_id

每个领导下有奖金的员工的最高工资(分组前的筛选)

SELECT MAX(salary), manager_id FROM employeesWHERE commission_pct IS NOT NULLGROUP BY manager_id

哪个部门的员工数量大于2(分组后的筛选:HAVING)

SELECT COUNT(*) as count , department_id FROM employees GROUP BY department_idHAVING count > 2

每个工种有奖金的员工最高工资大于12000的工种编号和最高工资

SELECT MAX(salary) as max, job_id FROM employees WHERE  commission_pct is not null GROUP BY job_idHAVING max > 12000

领导编号大于102 的员工最低工资大于5000的 领导

SELECT min(salary) as min, manager_id FROM employees WHERE manager_id > 102GROUP BY manager_idHAVING min > 5000

按表达式筛选

按员工姓名长度分组,查员工个数大于5的姓名长度有几个

SELECT count(*) as count, LENGTH(last_name) len from employees GROUP BY lenHAVING count > 5

多个字段分组

每个部门每个工种的平均工资

SELECT AVG(salary) as salary, job_id, department_id FROM employeesGROUP BY job_id, department_idORDER BY salary DESC

每个部门每个工种的平均工资中大于10000的

SELECT AVG(salary) as salary, job_id, department_id FROM employeesGROUP BY job_id, department_idHAVING salary > 10000ORDER BY salary DESC

查各工种平均最大最小和总和

select sum(salary) as sum,min(salary) as min,max(salary) as max,avg(salary) as avg,job_id FROM employeesGROUP BY job_idORDER BY job_id DESC

3.自定义函数

  1. 函数和存储过程的区别

    函数只能有一个返回,而且必须有返回,存储过程适合批量插入,更新,函数适合处理数据后,得到一个结果,适合查询。

  2. 创建

CREATE FUNCTION(参数列表) RETURNS 返回类型BEGIN  函数体(必须有return 语句)(函数体只有一句话,可以省略BEGIN END)END
delimiter $CREATE FUNCTION fun1() RETURNS INTBEGINDECLARE count INT DEFAULT 0;SELECT COUNT(*) INTO count FROM myemployees.employees;RETURN count;END $
delimiter $CREATE FUNCTION getPriceFromId(empId INT) RETURNS DOUBLEBEGINDECLARE price DOUBLE DEFAULT 0.0;SELECT employees.price INTO price FROM employees WHERE employee_id = empId;RETURN price;END $
  1. 调用
SELECT fun1();
  1. 查看函数
SHOW CREATE FUNCTION getPriceFromId
  1. 删除函数
DROP FUNCTION getPriceFromId
  1. 循环结构
  • WHILE :
标签:WHILE 条件 DO循环体END WHILE 标签;
  • LOOP:
标签: LOOP循环体;IF 退出条件 THENLEAVE 标签; END IF; END LOOP 标签;
  • REPEAT:
标签:REPEAT循环体UNTIL 结束条件 END REPEAT 标签;
  1. 循环控制语句:
  • ITERATE(继续,结束本次继续下次)

  • LEAVE (break,跳出所在循环)

  •  

MySQL开篇

栏目持续更新中

一、MySQL概述

MySQL数据库由瑞典MySQL AB公司开发。公司名中的”AB”是瑞典语”aktie bolag”股份公司的首字母缩写。该公司于2008年1月16日被SUN公司收购,2009年,SUN公司又被Oracle收购。因此,MySQL数据库现在属于Oracle公司。MySQL中的”My”是其作者Michael Widenius根据其大女儿My的名字来命名的。

本文系MySQL系列的开篇,主要基于MySQL8并部分结合5.7,主要介绍SQL语句和语法、MySQL的数据库对象、架构和性能调优以及一些高级特性的运用和原理等。

二、MySQL的优势

  • 可移植性

    MySQL数据库几乎支持所有的操作系统,如Linux、Solaris、FreeBSD、Mac和Windows。

  • 免费

    MySQL的社区版完全免费,一般中小型网站的开发都选择MySQL作为网站数据库。

  • 开源

    2000年,MySQL公布了自己的源代码,并采用GPL许可协议正式进入开源的世界。开源意味着可以让更多人审阅和贡献源代码,可以吸纳更多优秀人才的代码成果。

  • 关系型数据库

    MySQL可以利用标准SQL语法进行查询和操作。

  • 速度快、体积小、容易使用

    与其他大型数据库的设置和管理相比,其复杂程度较低,易于学习。MySQL的早期版本(主要使用的是MYISAM引擎)在高并发下显得有些力不从心,随着版本的升级优化(主要使用的是InnoDB引擎),在实践中也证明了高压力下的可用性。从2009年开始,阿里的”去IOE”备受关注,淘宝DBA团队再次从Oracle转向MySQL,其他使用MySQL数据库的公司还有Facebook、Twitter、YouTube、百度、腾讯、去哪儿等,自此,MySQL在市场上占据了很大的份额。

  • 安全性和连接性

    十分灵活和安全的权限和密码系统,允许基于主机的验证。当连接到服务器时,所有的密码传输均采用加密形式,从而保证了密码安全。因为MySQL是网络化的,所以可以在互联网上的任何地方访问,提高数据共享的效率。

  • 丰富的接口

    提供了用于C、C++、Java、PHP、Python、Ruby、Eiffel、Perl等语言的API。

  • 灵活

    MySQL并不完美,但是却足够灵活,能够适应高要求的环境。同时,MySQL既可以嵌入应用程序中,也可以支持数据仓库、内容索引和部署软件、高可用的冗余系统、在线事务处理系统等各种应用类型。

  • 存储引擎架构

    MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理及其他系统任务和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。MySQL中同一个数据库,不同的表格可以选择不同的存储引擎。其中使用最多的是InnoDB和MyISAM,MySQL5.5之后InnoDB是默认的存储引擎。

三、MySQL的版本

针对不同用户,MySQL提供了3个不同的版本。

  • MySQL Enterprise Server (企业版)

    能够以更高的性价比为企业提供数据仓库应用,该版本需要付费使用,官方提供电话技术支持。

  • MySQL Cluster (集群版)

    MySQL集群版是MySQL适合于分布式计算环境的高可用、高冗余版本。它采用了NDB Cluster存储引擎,允许在1个集群中运行多个MySQL服务器。它不能单独使用,需要在社区版或企业版基础上使用,集群版是免费的,但是高级集群版MySQL Cluster CGE需要付费。

  • MySQL Community Server (社区版)

    在开源GPL许可证之下可以自由地使用。该版本完全免费,但是官方不提供技术支持。本书是基于社区版讲解和演示的。在MySQL社区版开发过程中,同时存在多个发布系列,每个发布系列处在不同的成熟阶段。

MySQL 5.7(RC)是当前稳定的发布系列。RC版(Release Candidate 候选版本)只针对严重漏洞修复和安全修复重新发布,没有增加会影响该系列的重要功能。从MySQL 5.0、5.1、5.5、5.6直到5.7都基于5这个大版本,升级的小版本。5.0版本中加入了存储过程、服务器端游标、触发器、视图、分布式事务、查询优化器的显著改进,以及其他的一些特性。这也为MySQL 5.0之后的版本迈向高性能数据库的发展奠定了基础。

MySQL 8.0.26(GA)是最新开发的稳定发布系列。GA(General Availability 正式发布的版本)是包含新功能的正式发布版本。这个版本是MySQL数据库又一个新时代的开始。

四、MySQL基础篇

使用MySQL前需要进行安装,并进行简单配置(修改字符集,打开远程连接等),为了更加贴合实际应用场景,本章关于MySQL的一切,无特殊说明的,都基于运行在Linux系统环境上的MySQL。

Linux系统安装MySQL,最简单的方式就是采用rpm包安装,这里我采用常用的CentOS7环境来安装MySQL用于后续学习测试,具体步骤见:

除了rpm包安装,还可以通过自行编译源码的方式安装MySQL,因为CentOS7逐步不再更新,因此这里我尝试基于另一个RHEL系的Linux发行版RockyLinux9的环境来编译安装,具体步骤见:

MySQL的基础部分,主要包括以下内容:

SQL语言,数据类型、约束、DDL/DML语句以及SELECT语句等

序号文章名概述
1MySQL数据定义语言DDL语句以及数据类型
2MySQL插入修改和删除数据的添加和更新,DML语句
3MySQL查询数据的查询,SELECT语句,JOIN查询
4MySQL事务事务的特性,隔离级别,TCL语句

一些基础的数据库对象(视图、存储过程、函数、触发器、变量等)的使用

序号文章名概述
1MySQL函数常见函数使用和自定义函数
2MySQL存储过程存储过程的定义和调用
3MySQL视图视图创建、修改和使用
4MySQL变量用户变量和系统变量,变量的查询和设置

五、MySQL高级篇

本博客MySQL高级部分的内容,侧重点是后端开发中怎样写出更高性能的SQL,基本不会深入到DBA领域。

内容包括MySQL的字符集,语法模式,用户和权限DCL语句,MySQL架构和执行流程,索引和索引优化,锁机制,事务和日志以及主从复制等。

序号文章名概述
1MySQL字符集及底层原理基于MySQL5.7解读MySQL字符集和排序实现原理
2MySQL5.7x的主从复制负载均衡、备份和高可用性

结束语

更多MySQL相关的内容,可以查阅官方文档:

参考

  1. 《剑指MySQL 8.0:入门、精练与实战》,作者:尚硅谷教育,电子工业出版社,2023年2月
  2. 《高性能MySQL(第三版)》,作者:Baron Schwartz、Peter Zaitsev、Vadim Tkachenko,电子工业出版社,2013年5月
  3. MySQL数据库入门到大牛,作者:尚硅谷,哔哩哔哩,2021-11-17
  •