MySQL问题4
MySQL中varchar和char的区别
在 MySQL 中,VARCHAR
和 CHAR
都是用于存储字符串类型的字段,但它们在存储方式、性能、适用场景等方面存在明显区别:
1. 存储方式
类型 | 说明 |
---|---|
CHAR(n) | 定长字符串,始终占用固定 n 个字符空间。不足的会自动在右侧补空格(存储时),查询时会自动去除补空格。 |
VARCHAR(n) | 变长字符串,只占用实际字符长度 + 1 或 2 个字节(记录长度信息) |
举例:
CHAR(10): 存 'abc',存储为 'abc '(右侧补7个空格)
VARCHAR(10): 存 'abc',只占 3 + 1 = 4 个字节
2. 性能对比
特性 | CHAR | VARCHAR |
---|---|---|
读取速度 | 较快(定长,容易定位) | 略慢(需读取长度信息) |
更新性能 | 稳定(定长不易产生碎片) | 大量更新可能导致行迁移和碎片 |
空间占用 | 多(始终固定长度) | 少(根据实际长度变化) |
CHAR
读取更快,但除非数据长度始终一致,否则它占用空间较大。
3. 使用场景推荐
场景 | 推荐类型 |
---|---|
固定长度,如身份证号、邮编、MD5、UUID | CHAR |
可变长度,如名字、地址、备注等 | VARCHAR |
4. 其他注意事项
-
最大长度限制
CHAR
最多 255 个字符。VARCHAR
最多 65535 字节(注意字符集影响,如 utf8 每字符占 3 字节)。
-
行格式限制
- InnoDB 表中,如果
VARCHAR
太长,可能会被拆分存储到“溢出页”。
- InnoDB 表中,如果
口诀:
「定长用 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
:
id | name | age |
---|---|---|
1 | Alice | 15 |
2 | NULL | 25 |
3 | Charlie | NULL |
4 | NULL | NULL |
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) |
区别:
COUNT(*)
:最全、最快,连 NULL 也算。COUNT(1)
:等价于COUNT(*)
,但没优化优势。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, 70Step 3:顺着链表 → 页3 [80, 90] → 拿到 80,停止最终结果:60, 70, 80
特性 | 可视化图中的体现 |
---|---|
多路平衡 | 根节点拆成多个范围,指向多个子页 |
快速查找 | 每层只需一次判断(最多 2~4 层) |
范围查找快 | 叶子节点链表结构,顺着链表走 |
聚簇存储 | 叶子节点含完整行,不用回表 |