目录

MySQL问题4

MySQL问题4

MySQL中varchar和char的区别

在 MySQL 中,VARCHARCHAR 都是用于存储字符串类型的字段,但它们在存储方式、性能、适用场景等方面存在明显区别:


1. 存储方式

类型说明
CHAR(n)定长字符串,始终占用固定 n 个字符空间。不足的会自动在右侧补空格(存储时),查询时会自动去除补空格。
VARCHAR(n)变长字符串,只占用实际字符长度 + 1 或 2 个字节(记录长度信息)

举例:

CHAR(10):  'abc',存储为 'abc       '(右侧补7个空格)
VARCHAR(10):  'abc',只占 3 + 1 = 4 个字节

2. 性能对比

特性CHARVARCHAR
读取速度较快(定长,容易定位)略慢(需读取长度信息)
更新性能稳定(定长不易产生碎片)大量更新可能导致行迁移和碎片
空间占用多(始终固定长度)少(根据实际长度变化)

CHAR 读取更快,但除非数据长度始终一致,否则它占用空间较大。


3. 使用场景推荐

场景推荐类型
固定长度,如身份证号、邮编、MD5、UUIDCHAR
可变长度,如名字、地址、备注等VARCHAR

4. 其他注意事项

  • 最大长度限制

    • CHAR 最多 255 个字符。
    • VARCHAR 最多 65535 字节(注意字符集影响,如 utf8 每字符占 3 字节)。
  • 行格式限制

    • InnoDB 表中,如果 VARCHAR 太长,可能会被拆分存储到“溢出页”。

口诀:

「定长用 CHAR,变长用 VARCHAR;空间选 VARCHAR,速度选 CHAR。」


MySQL中count()、count(1)和count(字段名)的区别

在 MySQL 中,COUNT()COUNT(1)COUNT(字段名) 都是用于统计行数的聚合函数,但它们在统计逻辑、NULL 处理、性能优化等方面存在区别。

1 、三者区别

表达式含义是否统计 NULL 行典型用途
COUNT(*)统计所有行数(包括 NULL)表行数统计
COUNT(1)统计所有行数(包括 NULL),效果等同于 COUNT(*)和 COUNT(*) 一样
COUNT(字段名)统计字段值不为 NULL 的行数判断某列有多少有效值

示例:

假设有一张表 users

idnameage
1Alice15
2NULL25
3CharlieNULL
4NULLNULL
1. SELECT COUNT(*) FROM users;

返回 4,统计全部行。

2. SELECT COUNT(1) FROM users;

返回 4,和 COUNT(*) 一样,1只是个常量

3. SELECT COUNT(name) FROM users;

返回 2,因为只有两行 name 不是 NULL。

4. SELECT COUNT(age) FROM users;

返回 2,同样只统计非 NULL 的 age


2、性能区别

  • COUNT(*)最推荐使用的统计行数方式:

    • MySQL 优化器会做特别优化,不实际读取列,直接走统计信息。
  • COUNT(1) 理论上和 COUNT(*) 等价,区别不大,但没有 COUNT(*) 优化彻底。

  • COUNT(字段名) 性能较慢,因为需要判断字段是否为 NULL

用途推荐用法
统计表的总行数COUNT(*)
统计某列的非空数量COUNT(字段名)
代替 COUNT(*)(不推荐)COUNT(1)

区别:

  1. COUNT(*):最全、最快,连 NULL 也算。
  2. COUNT(1):等价于 COUNT(*),但没优化优势。
  3. COUNT(字段):只数非 NULL 的字段行。

MySQL的B+树中查询数据的全过程

假设我们有以下索引结构(id 是主键):

          [30 | 60]
         /    |    \
     [10 20] [40 50] [70 80 90] ← 叶子节点,存数据
  • 根节点 [30, 60]:索引 key,不存数据
  • 叶子节点:[10,20], [40,50], [70,80,90] 存储完整行数据(聚簇索引)
  • 每个节点就是一页(Page),约16KB

1、查询过程(以查询 id = 70 为例)

步骤一:从根节点开始搜索

  • 根节点 key 为 [30, 60]
  • 70 > 60 → 选择第三个子节点:[70, 80, 90]

步骤二:进入叶子节点

  • 叶子节点是 [70, 80, 90]
  • 在叶子节点内部做二分查找或顺序查找
  • 找到 id=70 对应的整行数据(聚簇索引)

查找完成!


2、全过程总结(图解)

           +-------------+
           |  30   60    |   ← 根节点 (非叶子)
           +-------------+
            /     |      \
     +----------+ +------+---------+
     |10 20 ... | |40 50 ...| 70 80 90 ← 叶子节点
     +----------+ +------+---------+
  • 查询 id=45

    • 根节点:[30,60] → 45 位于中间 → 走中间分支
    • 进入 [40,50] 节点 → 找到 45 或返回不存在

示例:B+树结构示意图

以一个主键索引为例(InnoDB 聚簇索引),建表如下:

CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(20)
);

假设表中数据如下(id 是主键):

id: 10, 20, 30, 40, 50, 60, 70, 80, 90

经过 B+ 树组织后,大概结构如下:

                             [40]
                          /       \
               [10, 20, 30]       [50, 60, 70] --------> [80, 90]
                (页1)              (页2)                    (页3)
  • [] 表示一个页(Page),每页约16KB,包含多个 key
  • 叶子节点之间通过链表相连(→),支持范围查找
  • 中间节点只存索引 key,不存数据
  • 叶子节点存完整行数据(id, name)

查询过程可视化:查找 id = 70

Step 1:从根节点开始
┌───────────────┐
│     [40]      │ ← 根节点(非叶子)
└───────────────┘
        │
        └── id > 40,向右走

Step 2:进入右子树
┌────────────────────┐
│   [50, 60, 70]      │ ← 叶子节点,页2
└────────────────────┘

Step 3:在页2中顺序查找
   找到了 id = 70,对应整行数据 name = 'Tom'

查询完成,最多访问两页内存/磁盘页。


范围查询过程:查找 id BETWEEN 60 AND 85

Step 1:从根节点 [40] 开始,id > 40 → 右子树

Step 2:访问页2 [50, 60, 70] → 拿到 60, 70

Step 3:顺着链表 → 页3 [80, 90] → 拿到 80,停止

最终结果:60, 70, 80

特性可视化图中的体现
多路平衡根节点拆成多个范围,指向多个子页
快速查找每层只需一次判断(最多 2~4 层)
范围查找快叶子节点链表结构,顺着链表走
聚簇存储叶子节点含完整行,不用回表