咸亨酒店的账房玄机---从孔乙己赊账窥MySQL查询之道
📖 开篇:咸亨酒店开业
在鲁迅笔下的咸亨酒店里,掌柜对孔乙己"赊账多少"的查询应答,恰似MySQL在内存与磁盘间的数据流转。让我们透过长衫客赊酒的场景,揭开数据库存储架构的神秘面纱。这日刚开业,孔乙己踱着方步来到柜台前:"温两碗酒,要一碟茴香豆。"说着排出九文大钱,忽然压低声音:"敢问掌柜的,我总共赊的账,可还...记得清?"
掌柜的闻言一惊,看着眼前这个穿着长衫站着喝酒的人,突然意识到:要快速查清陈年旧账,看来要费一番周转了。这场景像极了MySQL在内存与磁盘间的数据查询之旅...
让我们查看掌柜的账本信息
sql
CREATE TABLE bill_record (
bill_id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
custormer VARCHAR(255) comment '顾客',
city VARCHAR(100) comment '顾客来源地',
bill_date DATE comment '日期',
bill_money DECIMAL(10,2) comment '金额',
bill_remark text comment '备注信息'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '账单';
insert into bill_record values(null,'阿Q','绍兴','2023-01-05',3,'茴香豆一碟');
insert into bill_record values(null,'孔乙己','绍兴','2023-12-19',3,'茴香豆一碟');
insert into bill_record values(null,'孔乙己','绍兴','2024-02-02',3,'茴香豆一碟');
insert into bill_record values(null,'祥林嫂','南通','2023-07-12',10,'一打酒');
select * from bill_record;
+---------+-----------+--------+------------+------------+-----------------+
| bill_id | custormer | city | bill_date | bill_money | bill_remark |
+---------+-----------+--------+------------+------------+-----------------+
| 1 | 阿Q | 绍兴 | 2023-01-05 | 3.00 | 茴香豆一碟 |
| 2 | 孔乙己 | 绍兴 | 2023-12-19 | 3.00 | 茴香豆一碟 |
| 3 | 孔乙己 | 绍兴 | 2024-02-02 | 3.00 | 茴香豆一碟 |
| 4 | 祥林嫂 | 南城 | 2023-07-12 | 10.00 | 一打酒 |
+---------+-----------+--------+------------+------------+-----------------+
🏮 第一幕:全表扫描的慢镜头(掌柜翻账本)
场景还原:
掌柜的抓耳捞腮,不断调用大脑记忆(Buffer Pool内存 ),他也无法记清具体赊账金额,只得让孔乙己稍等片刻随即转身后向柜台,很熟练的拿起那本泛黄的牛皮账本(磁盘数据文件),这本账册用蝇头小楷记录着:账册
sql
mysql> select * from bill_record where custormer='孔乙己';
+---------+-----------+--------+------------+------------+-----------------+
| bill_id | custormer | city | bill_date | bill_money | bill_remark |
+---------+-----------+--------+------------+------------+-----------------+
| 2 | 孔乙己 | 绍兴 | 2023-12-19 | 3.00 | 茴香豆一碟 |
| 3 | 孔乙己 | 绍兴 | 2024-02-02 | 3.00 | 茴香豆一碟 |
+---------+-----------+--------+------------+------------+-----------------+
mysql> explain select * from bill_record where custormer='孔乙己'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: bill_record
partitions: NULL
type: ref
possible_keys: idx_custormer_bill_date
key: idx_custormer_bill_date
key_len: 768
ref: const
rows: 2
filtered: 100.00
Extra: NULLtype: ALL -- 代表全表扫描
rows: 4 -- 代表扫描所有行
-- 查看慢日志
mysql> set global slow_query_log=on;
-- 查看服务器慢日志
# Time: 2025-03-06T03:36:52.169226Z
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.000232 Lock_time: 0.000099 Rows_sent: 2 Rows_examined: 4
SET timestamp=1741232212;
select * from bill_record where custormer='孔乙己';
Rows_sent: 2 -- 代表返回的行数
Rows_examined: 4 -- 代表检查的行数
查账三部曲:
- 掌柜思考 :掌柜遍历内存记忆看是否有孔乙己赊账信息(Buffer Pool),发现找不到相关记忆信息
- 逐页翻找 :掌柜拿账本到柜台,手指沾着唾沫从第一页翻到末页(磁盘I/O,全表扫描)
- 柜台对账 :一旦找到孔乙己赊账记录,掌柜并默记赊账金额(Buffer Pool),然后将数字打到算盘上,如此反复,直到翻到末页,算珠被打的劈里啪啦的响(CPU计算**)
- 告知结果 :掌柜的将最后算盘计算的数字告知孔乙己(返回结果)
账本太重太厚,账页从正头翻到尾,掌柜的鼻尖沁出汗珠子,忽地两眼放光:"终于找全了!共计六文!"(全表扫描耗费资源),话音未落,却见孔乙己倚着榆木柜台笑得前仰后合,长衫下摆扫落了三四粒茴香豆。掌柜若这穷酸书生日日来翻旧账,怕是要把柜台磨穿,看来务必想个办法了。
存储架构对照表
咸亨元素 | MySQL组件 | 协作规则 |
---|---|---|
掌柜的记忆 | Buffer Pool | 缓存最近访问的账页,默认占内存80% |
牛皮账本 | 磁盘 | 数据最终存储地,按.ibd文件分册 |
算盘 | CPU资源 | CPU计算 |
🏮 第二幕:二级索引显神通(朱红目录册的秘密)
掌柜的妙招:
昨日查账可让掌柜费了老大劲,掌柜在柜台边挂起一本朱红目录(二级索引),该朱红目录册小巧而精致。
sql
ALTER TABLE bill_record ADD INDEX idx_custormer_bill_date(custormer,bill_date);
索引结构揭秘:
这本目录示意:
css
阿Q
└─2023-01-05 → 主账册第23页
孔乙己
├─2023-12-19 → 主账册第21页
└─2024-02-02 → 主账册第89页
祥林嫂
└─2023-07-12 → 主账册第156页
查询过程:
sql
mysql> explain select * from bill_record where custormer='孔乙己'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: bill_record
partitions: NULL
type: ref
possible_keys: idx_custormer_bill_date
key: idx_custormer_bill_date
key_len: 768
ref: const
rows: 2
filtered: 100.00
Extra: NULL
type: ref -- 代表等值查询
key: idx_custormer_bill_date -- 代表使用的索引名称
key_len: 768 -- custormer(255[字段长度]*3[UTF8占用3字节] + 1[字段是否为空]+2[字段是否变长]) 代表使用的索引部分长度即前缀索引
# Time: 2025-03-06T03:41:24.764760Z
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.000218 Lock_time: 0.000094 Rows_sent: 2 Rows_examined: 2
SET timestamp=1741232484;
select * from bill_record where custormer='孔乙己';
Rows_sent: 2 -- 代表返回的行数
Rows_examined: 2 -- 代表检查的行数,证明通过索引锁定了所在的数据行
高效查账四步舞:
翌日,孔乙己又懒洋洋的跑过来,对掌柜说到"敢问掌柜的,我赊的账,可还...记得清?"
- 掌柜思考 :掌柜遍历自己大脑看是否有孔乙己赊账信息(Buffer Pool)
- 目录寻踪 :掌柜的实在记不起来,转身翻朱红目录(索引扫描)
- 精准定位 :"孔乙己-腊月"直指189页(索引命中)
- 取账验证 :只搬对应账页到柜台(回表查询1)
- 循环遍历 :掌柜的回到朱红目录册找到189页指向的下一条记录第245页(数据页指针)
- 取账验证 :只搬对应账页到柜台,如此反复,直到翻到末页,算盘也打的劈里啪啦的响(CPU计算)
- 当场确认 :"您赊了六文钱"(返回结果)
存储架构对照表
咸亨元素 | MySQL组件 | 协作规则 |
---|---|---|
掌柜的记忆 | Buffer Pool | 缓存最近访问的账页,默认占内存80% |
牛皮账本 | 磁盘 | 数据最终存储地,按.ibd文件分册 |
朱红目录册 | 二级索引 | 加速查询 |
算盘 | CPU资源 | CPU计算 |
掌柜这次掏出个巴掌大的小本子,不多时,并直接对孔乙己说到"共欠了六文!",孔乙己一脸愕然,掌柜敲了敲朱红目录册说到:"玄机在这里呢!"
🏮 第三幕:覆盖索引避回表(朱红目录册的玄机)
腊月的穿堂风卷着酒客的吆喝,查询账单的人越来越多,带着老花镜的掌柜一遍遍翻看这账册,这也累的够呛。掌故心想,这伙人只管自己赊账多少,我为何不将这个赊账信息直接放入到朱红目录册中,如此并无需翻账本了。
sql
alter table bill_record add index idx_custormer_bill_money(custormer,bill_money);
mysql> explain select sum(bill_money) from bill_record where custormer='孔乙己'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: bill_record
partitions: NULL
type: ref
possible_keys: idx_custormer_bill_date,idx_custormer_bill_money
key: idx_custormer_bill_money
key_len: 768
ref: const
rows: 2
filtered: 100.00
Extra: Using index
Extra: Using index -- 代表直接走索引,无需回表
# Time: 2025-03-06T06:15:33.006049Z
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.000240 Lock_time: 0.000097 Rows_sent: 1 Rows_examined: 0
SET timestamp=1741241733;
explain select sum(bill_money) from bill_record where custormer='孔乙己';
高效查账四步舞:
翌日,孔乙己又懒洋洋的跑过来,对掌柜说到"敢问掌柜的,我赊的账,可还...记得清?"
- 掌柜思考 :掌柜遍历自己大脑看是否有孔乙己赊账信息(Buffer Pool)
- 目录寻踪 :掌柜先翻朱红目录(索引扫描)
- 精准定位 :"孔乙己-腊月"直指189页(索引命中 ),获取本次赊账3文,手指灵活的拨动算盘的算珠(CPU计算)。
- 循环遍历:掌柜的回到朱红目录册找到189页指向的下一条记录第245页,获取本次赊账3文,手指灵活的拨动算盘的算珠。
- 当场确认 :"您共赊了六文钱"(返回结果)
存储架构对照表
咸亨元素 | MySQL组件 | 协作规则 |
---|---|---|
掌柜的记忆 | Buffer Pool | 缓存最近访问的账页,默认占内存80% |
朱红目录册 | 二级索引 | 加速查询 |
算盘 | CPU资源 | CPU计算 |
掌柜这次掏出个巴掌大的小本子,还没等算盘珠子拨响,张嘴就报数:"共欠了六文!"孔乙己吓得酒碗差点摔了:"您这新账本会算命吗?"掌柜敲了敲朱红目录册说到:"玄机还在这里呢!"
🏮 第四幕:掌柜的烦恼
掌柜看此朱红目录册如此甚好,根据不同的用途,一口气制作了十几分目录册,正当掌柜对此洋洋得意之时,弊端也暴露出来了。
- 索引不是万能的:就像给每粒茴香豆都做目录(过度索引)反而占地方
- 内存有限:柜台最多摆十本账(缓冲池大小限制)
- 索引实时更新:修改账本数据的时候也需要修改对应的朱红目录册数据(加重改数据代价)
💼 终章:孔乙己的顿悟
暮色中的咸亨酒店,孔乙己攥着刚结清的账本,忽然拍案道:"原来这查账的学问,不在茴字的四种写法,而在索引的精妙!"。掌柜笑着递过温好的黄酒:"客官可曾听过B+树?这索引目录的编排方式..." 话音未落,店内外充满了快活的空气。