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 層) |
范圍查找快 | 葉子節點鏈表結構,順著鏈表走 |
聚簇存儲 | 葉子節點含完整行,不用回表 |