建一张10万行的JSON表,在生成列city上建索引,对比直接查JSON路径表达式和查生成列的EXPLAIN与耗时,看生成列到底省在哪。
payload是JSON列,city是从payload里提取出来的生成列,在city上建索引。
建表插入CREATE TABLE t6 ( id INT PRIMARY KEY AUTO_INCREMENT, payload JSON, city VARCHAR(50) GENERATED ALWAYS AS (payload->>'$.city') STORED, INDEX idx_city(city) ); 插入10万行,payload是{"city": "...", "age": ...}这样的JSON
city列不是手动写入的,是MySQL根据 payload->>'$.city' 这个表达式,在每次INSERT/UPDATE时自动计算出来、存到磁盘上的(STORED)。它上面能像普通列一样建索引。
一种写法直接对JSON字段用路径表达式过滤,一种写法过滤生成列本身。
EXPLAIN结果EXPLAIN SELECT id FROM t6 WHERE payload->>'$.city' = 'Shenzhen' → Index lookup on t6 using idx_city (city = 'Shenzhen') (cost=4808 rows=48076) EXPLAIN SELECT id FROM t6 WHERE city = 'Shenzhen' → Covering index lookup on t6 using idx_city (city = 'Shenzhen') (cost=5108 rows=48076)
意外发现:哪怕WHERE条件写的是 payload->>'$.city' 这个JSON路径表达式,压根没提到"city"这个生成列的名字,MySQL优化器依然认出了这个表达式跟生成列的定义完全一致,直接用上了 idx_city 索引——不需要你在SQL里手动写"city"这个列名。
两条查询EXPLAIN显示都走了idx_city,但一个是"Index lookup"一个是"Covering index lookup"——耗时差多少。
EXPLAIN ANALYZE耗时WHERE payload->>'$.city' = 'Shenzhen' → 耗时 16.8 ms WHERE city = 'Shenzhen' → 耗时 4.63 ms (快约3.6倍)
原因就藏在EXPLAIN那两行结果的措辞差异里:查生成列city时是"Covering index lookup"——city本身就在索引里,查id(主键)也不需要回表,整条查询完全不碰聚簇索引。但查JSON路径表达式时是普通"Index lookup"(没有Covering)——虽然索引帮忙快速定位到了匹配的记录,但MySQL还要额外回到聚簇索引,把完整的payload列读出来,重新计算一次 payload->>'$.city' 去验证结果是否真的匹配,这一步的JSON解析和回表开销,就是3.6倍耗时差距的来源。
每一步都是本文上面用EXPLAIN和EXPLAIN ANALYZE跑出来的原始结果。
EXPLAIN / EXPLAIN ANALYZE:对比表达式索引与生成列索引