“图形已经在缓存中。为什么架构页面仍然需要几秒钟才能显示?”
这个问题导致我们在 PmaControl 中进行了一个非常简短的修复。 没有缺失的索引。也不需要重新计算绘图。 MariaDB 选择了一种昂贵的方式来查找要显示的图形的生成。
以下是此比较的指标、SQL 和限制。我们区分提出问题的客户端和已经快速执行初始查询的测试实例。
更新于 2026 年 10 月 10 日。 下文加入了下午 1:29 记录的 SQL 测量结果。由于 10 月 9 日截图时使用的连接不稳定,我们又在下午 1:45 左右从局域网内的一台服务器重新测量了下载时间。每组测试均保留各自的环境、图表数量和测量范围:SQL、HTTP 下载或 Chromium 页面加载。
用装图纸的盒子来解释
想象一下一个装满盒子的图书馆。每个盒子包含一代拓扑的绘图。绘图已经完成:这是我们的 SVG 缓存。
要显示页面,应用程序仍需找到正确的盒子、取出其中的图纸,再把它们排列到屏幕上。图书馆的目录已经存在,这就是 SQL 索引。
但仅有目录还不够。在速度缓慢的情况下,MariaDB 选择迭代数百万个条目来准备中间匹配列表。对于我们感兴趣的验证,找到单个匹配就足够了。
修复明确表达了这一检查。返回的图纸完全相同,而查找过程避开了代价高昂的绕行。
已经缓存的内容
SVG 保存在 dot3_graph 中。 dot3_cluster 表将它们与拓扑生成相关联。该页面已经使用 单个 SQL 查询 检索了所有图表,该查询特别返回:
SELECT dg.id, dg.svg, dg.height, dg.width,
(dg.height * dg.width) AS area,
dc.date_inserted AS date_refresh
FROM dot3_cluster dc
JOIN dot3_graph dg ON dg.id = dc.id_dot3_graph
-- 该条件选择已准备好显示的代次。
因此,浏览器会收到图形及其高度、宽度和面积。App/Webroot/js/Architecture/topology.js 中的代码已经负责在浏览器中排布图形。本次修复没有修改这一算法。
缓存避免在每次访问时进行绘图。它不会避免搜索、传输和显示绘图。此 SVG 缓存与 MariaDB 查询缓存和浏览器的 HTTP 缓存是分开的。
不,没有缺失索引
正如 SHOW INDEX FROM dot3_cluster 所确认的,慢速实例已经具有此唯一索引:
-- 现有索引,以定义形式显示:
UNIQUE KEY id_dot3_information_2
(id_dot3_information, id_dot3_graph)
搜索过滤 id_dot3_information,即索引的第一列。因此,这使得找到一代的簇成为可能。
问题出在执行计划,即 MariaDB 执行 SQL 时选择的策略。初始版本使用 EXISTS 检查某一代是否至少包含一个集群。在这个实例上,该选择触发了物化:MariaDB 扫描约 433 万个历史索引项来准备中间结果。
这并不意味着浏览器收到了四百万张图表;它实际收到的是 61 张图表。过量工作发生在数据库中,早于有用结果的返回。433 万描述的是首次诊断时发现的扫描量级,并非传给 PHP 的行数。10 月 10 日的测量显示,该实例的表目前约有 520 万行;因此,初始文章与新一轮测试的数据量并不完全相同。
在非常宽的扫描期间可以使用索引。因此,索引的存在本身并不能保证短访问。
MariaDB、InnoDB / RocksDB 配置和表
两个环境都使用 MariaDB 11.8.9,但软件包不同:根据 10 月 10 日的记录,客户环境使用 11.8.9-deb13;测试实例使用 11.8.9-MariaDB-deb12-log,并已在服务器上直接核实。客户使用 PHP 8.4 重放 SQL;测试实例使用 FrankenPHP / PHP 8.5.9。
下图涵盖了页面上 16 个查询使用的 16 个应用程序表,超出了 SVG 查询中的三个表。有用的列、键和引擎来自测试实例的实际图表,记录于10 月 10 日下午 3:06。表存在检查还查询 information_schema.tables,这是一个元数据视图,不是分类为 InnoDB 或 RocksDB 的应用程序表。


打开矢量图大。实线表示已声明的外键;虚线表示 SQL 使用的逻辑连接。没有显示链接的表将单独查询。该图显示了关系,而不是查询执行的顺序。
| 测试实例中 SVG 查询涉及的表 | 观察到的存储引擎 |
|---|---|
dot3_information |
RocksDB |
dot3_cluster |
InnoDB |
dot3_graph |
InnoDB |
页面上的另外 13 个表均使用 InnoDB。上方的完整图表和可下载的存储引擎与大小清单列出了详细信息。
因此,SVG 查询在测试实例上跨越两个存储引擎。dot3_information 的可用性索引属于 RocksDB;dot3_cluster 的唯一索引以及 dot3_graph 中的 SVG 数据属于 InnoDB。修复保留了这些引擎和索引。本轮测试比较的是两种 SQL 写法,并未测量将表从 InnoDB 转换为 RocksDB 的效果。客户的测量记录没有提供其存储引擎,因此我们不会把测试实例上观察到的结构归于客户环境。
测试实例的匿名 .cnf 配置按加载顺序汇总了各包含文件中的服务器配置段,并删除了网络标识和机密。由于后面的文件可能覆盖前面的设置,实际读取到的全局变量给出了最终生效值,其中包括 2 GiB InnoDB 缓冲池、2 GiB RocksDB 块缓存、最多 128 个连接、tmp_table_size 和 max_heap_table_size 各 64 MiB,以及已禁用的查询缓存。
这些参数采集于下午 3:14,晚于下午 1:45 的测量;部分变量可能动态变化。.cnf 资源描述的是观察到的配置及其覆盖关系,并删去了敏感值,不能直接作为配置文件应用。测量记录中没有客户的 .cnf 配置。
SQL 修改前后
改动位于 App/Library/Dot3LatestGeneration.php,用于选择最新且已准备好的代次。
前 :
AND EXISTS (
SELECT 1
FROM dot3_cluster dc_ready
WHERE dc_ready.id_dot3_information = di.is_svg_generated
)
后 :
AND (
SELECT dc_ready.id_dot3_information
FROM dot3_cluster dc_ready
WHERE dc_ready.id_dot3_information = di.is_svg_generated
LIMIT 1
) IS NOT NULL
为什么这里是等价的?外部请求已施加 di.is_svg_generated > 0。如果存在匹配的簇,则子查询返回该正标识符。否则,没有结果的标量子查询将返回 NULL。因此,该条件始终检查这一代的簇是否存在。
标量子查询避免了使用 EXISTS 观察到的物化计划。引擎可以搜索索引中的代并在第一个匹配处停止。如果没有可用的现成一代,则使用的后备解决方案保持不变。
这并不是说 EXISTS 很慢的规则。 在我们的另一个实例上,旧查询已经使用了高效的 FirstMatch 策略。重写所有 EXISTS 将是一个糟糕的结论:您必须检查和测量每个查询的计划。
一位客户:10 月 10 日的新 SQL 测量
在一位客户处于下午 1:29 左右进行的测量,使用生产数据对每种写法各执行十次;排除一次预热,并交替执行修复前后的查询。生产代码已经包含修复;这里的“之前”是为比较而执行旧的 EXISTS 查询,“之后”则是带 LIMIT 1 的标量子查询。
| SQL 测量 | 之前 (EXISTS) |
之后 (LIMIT 1) |
|---|---|---|
| SVG 查询,最小值–最大值 | 4 434,17–5 297,38 ms | 2,69–6,20 ms |
| SVG 查询,中位数 | 5 044,59 ms | 3,32 ms |
| 16 个查询合计,中位数 | 5 068,52 ms | 12,92 ms |
| 返回的图表 | 61 | 61 |
| SVG 体积 | 995 307 字节 | 995 307 字节 |
| 本次测量的索引读取计数器 | 5 199 176 | 3 |
在本次测量中,主要查询从 5.04 秒降至 3.32 毫秒,约快 1,520 倍。修复前后的 61 个图形完全相同。当前体积与首次诊断记录的 995,437 字节相差 130 字节;两者采集日期不同,而新一轮测试的两种写法返回了相同内容。
修复前的 EXPLAIN 计划对物化过程估算为 4,406,092 个条目;修复后,使用索引的相关子查询估算为 81 个条目。这些估算值不是实际读取次数。本次测量的索引读取计数器显示为修复前 5,199,176 次、修复后 3 次;当时 dot3_cluster 包含 5,198,990 行。执行计划估算、实际读取计数和传给浏览器的图形数量是三种不同指标。
其他 15 个请求的中值均保持在 10 毫秒以下。有些波动:例如,警报计数从 9.21 毫秒到 0.85 毫秒,之前偶尔会达到 15.55 毫秒。此结果并不表明修复加快了计数速度:服务器的负载约为 6,并且这些请求紧随 5 秒的请求之后。代码更改仅涉及 SVG 的选择。
此活动重播的唯一更改请求(会话刷新)已执行,然后被取消。 该报告衡量 SQL;它不测量下载或完整的浏览器加载。 时间包括 mysqli 的完整检索结果,包括大约 1 MB 的 SVG。
| 客户环境中的查询 | 之前(毫秒) | 之后(毫秒) |
|---|---|---|
| 1。阅读登录会话 | 0,34 | 0,34 |
| 2。刷新会话,然后取消 | 0,16 | 0,19 |
| 3。加载 61 图表及其尺寸 | 5 044,59 | 3,32 |
| 4。列出客户 | 0,53 | 0,29 |
| 5。列出环境 | 0,78 | 0,15 |
| 6。列表标签 | 0,60 | 0,22 |
| 7。搜索导航提要 | 1,59 | 0,64 |
| 8。构建菜单 | 5,26 | 4,24 |
| 9。统计活动警报 | 9,21 | 0,85 |
| 10。选择菜单项 | 0,78 | 0,66 |
| 11。检查翻译键 | 0,53 | 0,15 |
| 12。阅读翻译 | 0,48 | 0,18 |
| 13。查看配置表 | 0,20 | 0,17 |
| 14。读取许可证设置 | 0,17 | 0,15 |
| 15。检查隐藏横幅表 | 0,18 | 0,15 |
| 16。阅读许可证横幅的屏蔽 | 0,30 | 0,13 |
| 每次运行的总和,中位数 | 5 068,52 | 12,92 |
包含时间范围和两份执行计划的匿名 SQL 测量记录补充了此表。
客户下载时间:比较假设
为了完成 HTTP 比较,我们从服务器获取测试实例上测得的下载时间,如下节所示。我们假设这些持续时间对于客户来说是相同的。 这些值是两种环境共同的假设;它们不构成客户端的新 HTTP 措施。
| 作为假设沿用的 HTML 文档下载时间 | 之前 | 之后 |
|---|---|---|
| 测试实例上的 Chromium 中位数 | 42,90 ms | 37,45 ms |
客户环境的首字节时间和完整页面加载时间没有重新测量,也不能直接套用测试实例的总时间,因为修复前仅客户的 SVG 查询就耗时五秒。我们将实际测得的 SQL 时间与假设的传输时间分开,不虚构浏览器加载时间。
下载和浏览器:来自服务器的新测量
为消除早期截图所用连接带来的不确定性,我们在10 月 10 日下午 1:45 左右进行了另一轮测试:从一台 Debian 12 服务器访问局域网内的测试实例。测试使用同一个未筛选的架构页面、同一个临时只读账户、30 个图形、1440 × 900 像素视口和 Chromium 152。浏览器 HTTP 缓存已禁用;每种写法排除一次预热,随后保留修复前十次和修复后十次加载。
目标服务器使用 MariaDB 11.8.9 和 FrankenPHP / PHP 8.5.9。应用代码对应提交 4207dfbe5e7d1709dbbd2efdd2cff8c1e6619dc1;在“之前”版本中,仅 Dot3LatestGeneration.php 取自其父提交 366b200703ff7550ec01d33e2c7d49c0b570ab15。测量通过局域网内临时提供工作树内容的 HTTPS 服务器完成。生产环境仍运行修复后的代码。
中位数(以毫秒为单位):
| Chromium 中测量的阶段 | 之前 (EXISTS) |
之后 (LIMIT 1) |
|---|---|---|
| 第一个字节,包括 PHP 和传输 | 62,35 | 50,90 |
| 下载 HTML 文档 | 42,90 | 37,45 |
收到文件 → 事件 load |
416,05 | 392,20 |
满载 → load |
523,15 | 488,40 |

文档下载的变化范围为之前 33.2 到 61.3 毫秒,之后 32.3 到 55.1 毫秒。满载变化范围为前472.3 至 681.5 毫秒,后444.5 至 669.7 毫秒。这些系列重叠:它们在此过程中显示了大约半秒的加载,而没有建立 34.75 毫秒的可重现浏览器增益。
显示的 30 个 SVG 在 20 个负载中的每一个上都具有相同的 SHA-256。两种变体的解压缩文档大小均为 2,240,909 字节。其压缩后的主体大约为671,254 字节,或者按 Chromium 计算,包含标头大约为 671,554 字节。该 HTML 卷和这 30 个图表属于测试实例;它们不对应于客户端 61 个图形的 995,307 字节 SVG。
另一次独立的 curl --compressed 检查在预热后对每种写法发送十个请求,确认传输时间仍有波动:下载中位数为修复前 70.85 毫秒、修复后 93.80 毫秒,完整 HTTP 响应为 119.97 和 145.62 毫秒。curl 不加载页面资源,也不执行 JavaScript。解压缩、HTTP 客户端、协商出的协议和服务器负载都会造成与 Chromium 的差异;这些差异不能证明 SQL 发生了回退。
每次访问都会测量里程碑,然后单独汇总:中位数之和不一定是总数的中位数。收到文档后的时间包括其他资源、HTML解析、JavaScript、渲染。我们不仅仅将其归因于图表的位置。第一个字节已经包含了 PHP 准备;预计 PHP 时间不会添加到总数中。
12,948、2,952 和 843 毫秒的旧加载时间仍是 10 月 9 日的历史观测值,可在存档 CSV 中查看。其 470 至 535 毫秒的下载时间受当时不稳定连接影响,已不再作为网络基准。新的 30 图形测试无法重建 61 图形实例的浏览器加载时间。
测试实例上的新 SQL 比较(单个只读快照中的 10 个交替对,并且为会话禁用了 MariaDB 查询缓存)的结果是之前 6.94 毫秒,之后 5.81 毫秒。完整结果和 2,172,396 字节的 SVG 相同。最初的计划仍然使用 FirstMatch:即使 dot3_cluster 中有 6,591,509 行,该实例也不会重现其他服务器昂贵的实现。因此,仅表的大小不足以预测速度缓慢。
测试实例:10 月 9 日的 16 个查询明细
下方明细表保留了 10 月 9 日的检测结果:使用未筛选页面、只读账户和 30 个图形。排除首次预热加载后,每个版本保留五次加载。该表并非 10 月 10 日对 16 个查询的新测量。
应用程序分析器在十次加载的每一次都记录到16 个查询。下表每一行对应一个在修复前后各执行一次的查询,最后一行给出总计。所列时间是 PHP 驱动端测得的 SQL 调用中位数,单位为毫秒,并不只代表 MariaDB 引擎内部耗时。
| 查询 | 之前(毫秒) | 之后(毫秒) |
|---|---|---|
| 1。阅读登录会话 | 0,31 | 0,34 |
| 2。刷新会话 | 0,29 | 0,27 |
| 3。加载 30 图表及其尺寸 | 5,86 | 5,57 |
| 4。列出客户 | 0,37 | 0,34 |
| 5。列出环境 | 0,23 | 0,27 |
| 6。列表标签 | 0,23 | 0,29 |
| 7。搜索导航提要 | 0,71 | 1,07 |
| 8。构建菜单 | 3,02 | 3,04 |
| 9。统计活动警报 | 1,11 | 1,09 |
| 10。选择菜单项 | 0,67 | 0,67 |
| 11。检查翻译键 | 0,33 | 0,31 |
| 12。阅读翻译 | 0,30 | 0,29 |
| 13。查看配置表 | 0,33 | 0,31 |
| 14。读取许可证设置 | 0,22 | 0,23 |
| 15。检查隐藏横幅表 | 0,32 | 0,31 |
| 16。阅读许可证横幅的屏蔽 | 0,18 | 0,22 |
| 总计,中位数 | 14,53 | 14,59 |
总计是为每个负载计算的总和的中位数。将每行的中位数相加不一定会给出该值。
在此测试实例中,没有表现出显着的整体 SQL 增益。 0.06 毫秒的差异是最小的。这说明:MariaDB 已经为旧查询选择了一个好的计划。该修复针对的是生产实例上遇到的成本高昂的计划。
此计数涵盖了分析器记录的三个连接上初始 PHP 呈现的应用程序请求。它不包括后续 AJAX 调用和内部登录命令。这 16 个请求不应分配给所有用户配置文件或所有安装。
另请检查结果是否正确
只有显示正确图形时,更快的页面才有意义。验证覆盖修复前后结果是否一致、代次已准备或缺失时的行为,以及后备路径。
使用 MariaDB CTE 中定义的测试数据执行了九个场景,而没有插入到应用程序表中。 SQL 比较还涵盖未过滤页面、过滤页面和图形聚合,并在同一事务快照中比较结果。放置 JavaScript 测试验证了跨多个屏幕宽度的行为。
测试实例上另一组十五对配对测量显示,筛选和聚合路径分别有约 0.34 ms 和 0.16 ms 的小幅中位开销。我们将这些结果纳入评估:修复并不会让所有场景都更快。这些针对性检查不能替代完整的产品测试套件。
在另一页上重做诊断
首先区分三个问题:服务器需要多长时间准备响应、文档需要多长时间才能到达以及浏览器需要多长时间才能完成加载?
然后计算请求并衡量每一个。对于占主导地位的计划,比较之前/之后的计划以及结果的平等性。特别检查搜索的条目数、中间结果以及索引的有效使用。重复测量:单次观察可能会产生误导。
在这种情况下,查询仍然是查询,并且索引保持不变。查找已存在于缓存中的绘图的方式已发生变化。
详细数据和SQL
- 16 个查询、它们在代码中的位置以及之前/之后的完整 SQL
- 十个检测负载:CSV 中每个请求的持续时间
- 10 月 10 日从服务器发起的 20 次 Chromium 加载,CSV 格式
- 10 月 10 日的 HTTP curl 检查:修复前和修复后
- 10 月 10 日测试实例上的 SQL 比较:JSON 格式的结果和计划
- 来自客户端的匿名 SQL 语句:16 个查询、范围和计划
- 测试实例的匿名 MariaDB
.cnf配置 和 活动变量、版本和包含文件 - SVG 中 16 个表的架构、SQL 定义、CSV 中的引擎和大小清单 和JSON 中的列、索引和关系
- SQL 脚本,以文本形式提供、Chromium 脚本、HTTP 脚本 和 临时 HTTPS 服务器配置
- 重现10月10日活动的方法和步骤
- 存档:10 月 9 日连接不稳定的三个生产负载
会话和帐户 ID 由可下载示例中的参数替换。生产和测试测量是分开的,以便在不混合两个环境的情况下对其进行解释。
评论 (0)
暂无评论。
发表评论