前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SQL如何在数据库中执行

SQL如何在数据库中执行

作者头像
JavaEdge
发布2023-01-06 09:10:54
3.1K0
发布2023-01-06 09:10:54
举报
文章被收录于专栏:JavaEdgeJavaEdge

数据库的服务端,可分为执行器(Execution Engine)存储引擎(Storage Engine) 两部分:

  • 执行器负责解析SQL执行查询
  • 存储引擎负责保存数据

1 SQL如何在执行器中执行

代码语言:javascript
复制
# 查询用户ID大于50的用户的所有订单
SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id
FROM users u INNER JOIN orders o ON u.id = o.user_id
WHERE u.id > 50

很简单的一个联查。DB收到查询请求后,先解析SQL语句,把这一串文本解析成便于程序处理的结构化数据,这是通用的语法解析过程。跟编程语言的编译器编译时,解析源代码过程一样。

转换后的结构化数据,就是抽象语法树(AST,Abstract Syntax Tree)。上面这SQL的AST:

img
img

执行器解析AST后,生成一个逻辑执行计划,即如何一步步执行查询和计算,最终得到执行结果的一个分步骤的计划。这个逻辑执行计划:

代码语言:javascript
复制
LogicalProject(user_id=[$0], user_name=[$1], order_id=[$5])
    LogicalFilter(condition=[$0 > 50])
        LogicalJoin(condition=[$0 == $6], joinType=[inner])
            LogicalTableScan(table=[users])
            LogicalTableScan(table=[orders])

和SQL、AST不同,这个逻辑执行计划已经很像可以执行的程序代码了。这执行计划像编程语言的函数调用栈,外层方法调用内层方法。所以,得从内往外看:

  1. 最内层的2个LogicalTableScan:把USERS和ORDERS这两个表的数据都读出来
  2. 拿这两个表所有数据做一个LogicalJoin,JOIN条件:第0列(u.id)=第6列(o.user_id)
  3. 再执行一个LogicalFilter过滤器,过滤条件:第0列(u.id)>50
  4. 做个LogicalProject投影,只保留第0(user_id)、1(user_name)、5(order_id)三列。“投影(Project)”:把不需要的列过滤

把这个逻辑执行计划翻译成代码,然后按照顺序执行,就正确查询出数据。但按执行计划,要执行2个全表扫描,再把2个表的所有数据做一个JOIN操作,性能差。

如user表1,000条数据,订单表10,000条数据,JOIN要遍历行数1,000 x 10,000 = 10,000,000行

这种从SQL的AST直译过来的逻辑执行计划,一般性能差,所以,要对执行计划优化。不同DB不同优化方法,优化总体思路:在执行计划中,尽早减少须处理的数据量。即尽量在执行计划最内层减少要处理的数据量。

简单优化后的逻辑执行计划:

代码语言:javascript
复制
LogicalProject(user_id=[$0], user_name=[$1], order_id=[$5])
    LogicalJoin(condition=[$0 == $6], joinType=[inner])
        LogicalProject(id=[$0], name=[$1])              // 尽早执行投影
            LogicalFilter(condition=[$0 > 50])          // 尽早执行过滤
                LogicalTableScan(table=[users])
        LogicalProject(id=[$0], user_id=[$1])           // 尽早执行投影
            LogicalTableScan(table=[orders])

对比原始逻辑执行计划,两点简单优化:

  1. 尽早执行投影,去除不需要的列
  2. 尽早执行数据过滤,去除不需要的行

JOIN前,把要JOIN的数据尽量减少,显然比原始执行计划快。

到这,执行器只在逻辑层分析SQL,优化查询执行逻辑,执行计划中操作的数据,仍是表、行和列。在数据库中,表、行、列都是逻辑概念,所以,这个执行计划叫“逻辑执行计划”。

执行查询接下来的部分,涉及数据库的物理存储结构。

2 SQL是如何在存储引擎中执行

数据真正存储时,无论在磁盘or内存中,都没法直接存储这种带行列的二维表。数据库中的二维表存储就是存储引擎负责,存储引擎主要功能就是把逻辑的表行列,用合适物理存储结构保存到文件。

不同数据库,物理存储结构完全不一样,各种数据库之间巨大性能差距的根本原因。MySQL在设计层对存储引擎抽象,存储引擎可替换。默认InnoDB,InnoDB中数据表的物理存储结构是以主键为关键字的B+树,每行数据直接就保存在B+树的叶节点。如上面的订单表组织成B+树:

img
img

这树以订单表的主键orders.id为关键字组织,其中“62:[row data]”,表示的是订单号为62的一行订单数据。在InnoDB中,表的索引也是以B+树的方式来存储的,和存储数据的B+树的区别是,在索引树中,叶子节点保存的不是行数据,而是行的主键值。

若通过索引检索记录,需先后查询索引树、数据树两棵树:

  • 先在索引树检索到行记录的主键值
  • 再用主键值去数据树中去查找这行数据

优化后的逻辑执行计划将会被转换成物理执行计划,物理执行计划和数据的物理存储结构相关。InnoDB直接将逻辑执行计划转换为物理执行计划:

代码语言:javascript
复制
InnodbProject(user_id=[$0], user_name=[$1], order_id=[$5])
    InnodbJoin(condition=[$0 == $6], joinType=[inner])
        InnodbTreeNodesProject(id=[key], name=[data[1]])
            InnodbFilter(condition=[key > 50])
                InnodbTreeScanAll(tree=[users])
        InnodbTreeNodesProject(id=[key], user_id=[data[1]])
            InnodbTreeScanAll(tree=[orders])

物理执行计划同样可以根据数据的物理存储结构、是否存在索引以及数据多少等各种因素进行优化。这一块儿的优化规则同样是非常复杂的,如把对用户树的全树扫描再按照主键过滤这两个步骤,优化为对树的范围查找:

代码语言:javascript
复制
PhysicalProject(user_id=[$0], user_name=[$1], order_id=[$5])
    PhysicalJoin(condition=[$0 == $6], joinType=[inner])
        InnodbTreeNodesProject(id=[key], name=[data[1]])
            InnodbTreeRangeScan(tree=[users], range=[key > 50])  // 全树扫描再按照主键过滤,直接可以优化为对树的范围查找
        InnodbTreeNodesProject(id=[key], user_id=[data[1]])
            InnodbTreeScanAll(tree=[orders])

按优化后的物理执行计划,一步步执行查找和计算,就得到SQL查询结果。

知道InnoDB索引实现后,就很容易明白为何主键不能太长,因为表的每个索引保存的都是主键值,过长主键会导致每个索引都很大。

代码语言:javascript
复制
SELECT * FROM user WHERE left(department_code, 5) = '00028';
SELECT * FROM user WHERE department_code LIKE '00028%';

为什么一个不能命中索引,一个能命中?InnoDB对物理执行计划进行优化的时候,能识别LIKE这种过滤条件,转换为对索引树的范围查找。第一条SQL,优化规则就没那么“智能”。

它并没有识别出来,这条件同样可转换为对索引树的范围查找,而走全表扫描。并不是说第一个SQL写不好,而是数据库不智能。能做的就是了解数据库脾气,按它能力,尽量写出它能优化的SQL。

总结

一条SQL在数据库中执行,经过语法解析成AST,然后AST转换为逻辑执行计划,逻辑执行计划经优化后,转换为物理执行计划,再经物理执行计划优化后,按照优化后的物理执行计划执行完成数据的查询。数据库都由执行器存储引擎两部分组成:

  • 执行器负责执行计算
  • 存储引擎负责保存数据
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 SQL如何在执行器中执行
  • 2 SQL是如何在存储引擎中执行
  • 总结
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档