你有这么高效的MySQL版本号排序,记住我给出的原理

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 在MySQL中,对版本号进行正确排序遇到了问题,表现为不符合常规的版本比较逻辑,如“1.0.12”被错误地排在“1.0.2”之后。这可能是由于接口在处理版本号比较时的算法或逻辑错误。为解决这个问题,提出了四个优化方案。

你有这么高效的MySQL版本号排序的SQL,记住我给出的原理。入门学习MySQL的时候,就是给我讲课的老师,就是这么给我讲的:MySQL执行SQL语句过程

前情提要

在当前系统中,我们遇到一个关于版本检查接口返回结果排序不准确的问题。具体表现为,当接口返回多个软件版本号进行排序时,版本号“1.0.12”被错误地置于“1.0.2”之后等类似这样的情况,这种情况明显违背了通常的版本比较逻辑。这一现象揭示了接口在处理多级版本号排序时存在算法缺陷或逻辑误解,导致无法正确识别和比较版本间的实际层级关系。

模拟数据

为清晰阐述问题细节,以下是具体问题表现的进一步说明:

问题现象:

  • 👨🏻‍💻 不合理的版本排序实例:当接口提供的版本列表包含“1.0.12”与“1.0.2”两个版本时,返回的排序结果将“1.0.2”置于“1.0.12”之前,即:“1.0.2” > “1.0.12”。
  • 👨🏻‍💻 预期正确排序:根据常规版本号比较规则,版本号由左至右依次代表主版本号、次版本号和修订号。在比较时,应先比较最左侧的主版本号,若相同则比较次版本号,再相同则比较修订号。依据此规则,正确的排序应为:“1.0.12” > “1.0.2”,因为“1.0.12”的主版本号“12”大于“1.0.2”的主版本号“2”。

问题影响:

  • 👨‍💻 版本识别混乱:客户端或用户在接收到错误排序的版本列表后,可能会误解软件的实际更新进度,导致选择安装过时的版本或错过重要功能更新。
  • 👨‍💻 决策误导:开发者、运维人员或自动化脚本依赖于接口返回的版本排序进行升级决策时,错误的排序可能导致选择执行不必要的降级操作或遗漏必要的升级操作。
  • 👨‍💻 用户体验受损:用户对于版本更新流程的体验受到影响,可能会对系统的专业性和可靠性产生质疑。

优化需求:

为解决上述问题,我们需要对版本检查接口进行优化,确保其在返回版本列表时能准确遵循标准的版本号比较规则,即:

  • 👩🏻‍💻 逐级比较:对比版本号时,应从左至右分别比较主版本号、次版本号和修订号,依次判断其大小关系。
  • 👩🏻‍💻 优先级正确:主版本号的变化应视为最重要的更新,其次为次版本号,最后为修订号。在比较过程中,左侧位数的数值差异应优先决定版本间的相对位置。
  • 👩🏻‍💻 逻辑健壮性:接口应能适应各种合法的版本号格式(如带有预发布标签或构建号的版本),并确保在这些情况下的排序依然准确无误。

优化版本检查接口的关键在于重构其内部的版本号比较与排序逻辑,确保其严格遵循标准的版本号解析与比较规则,提供准确无误的版本排序结果,进而消除因版本识别混乱带来的各种潜在问题,提升系统的整体稳定性和用户体验。

数据准备

测试数据脚本如下:

/*
Navicat MySQL Data Transfer
Source Server         : 
Source Server Version : 
Source Host           : 
Source Database       : 
Target Server Type    : MYSQL
Target Server Version : 50736
File Encoding         : 65001
Date: 2024-04-19 10:59:44
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for test_version
-- ----------------------------
DROP TABLE IF EXISTS `test_version`;
CREATE TABLE `test_version` (
  `version` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of test_version
-- ----------------------------
INSERT INTO `test_version` VALUES ('1.0.2');
INSERT INTO `test_version` VALUES ('1.0.12');
INSERT INTO `test_version` VALUES ('2.0.34');
INSERT INTO `test_version` VALUES ('2.0.38');
INSERT INTO `test_version` VALUES ('2.10.1');
INSERT INTO `test_version` VALUES ('3.6.1');
INSERT INTO `test_version` VALUES ('3.5.8');
INSERT INTO `test_version` VALUES ('3.5.13');

解决办法

方案一:

使用自定义排序函数

💻 解决思路:

创建一个自定义函数,将版本号转换为整数,以便在排序时使用整数比较。

DELIMITER $$
CREATE FUNCTION VERSION_TO_INT(version VARCHAR(255)) RETURNS INT DETERMINISTIC
BEGIN
  DECLARE major, minor, patch INT;
  SET major = SUBSTRING_INDEX(version, '.', 1);
  SET minor = SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1);
  SET patch = SUBSTRING_INDEX(version, '.', -1);
  RETURN major * 10000 + minor * 100 + patch;
END $$
DELIMITER ;

👉 详细解释:

  1. 使用DELIMITER定义一个新的语句结束符,以便在定义函数时使用。
  2. 使用CREATE FUNCTION创建一个名为VERSION_TO_INT的自定义函数,该函数接受一个VARCHAR(255)类型的参数,并返回一个INT类型的值。
  3. 在函数内部,使用SUBSTRING_INDEX函数提取版本号的主要、次要和补丁部分,并将它们转换为整数。
  4. 将提取到的整数部分组合成一个整数,其中主要版本号乘以10000,次要版本号乘以100,补丁版本号保持不变。这样可以确保在比较时,主要版本号的优先级最高,次要版本号次之,补丁版本号最低。
  5. 使用RETURN语句返回计算出的整数值。
  6. 使用DELIMITER ;将语句结束符重置为默认值。
  7. 在查询中使用ORDER BY子句和自定义函数VERSION_TO_INT对版本号进行排序。

✅ 执行结果:

SELECT * FROM test_version
ORDER BY VERSION_TO_INT(version) DESC;
输出:
--------------------------------------------
3.6.1
3.5.13
3.5.8
2.10.1
2.0.38
2.0.34
1.0.12
1.0.2
--------------------------------------------

方案二:

使用内置函数SUBSTRING_INDEXCAST

💻 解决思路:

使用SUBSTRING_INDEX函数提取版本号的主要、次要和补丁部分,并使用CAST函数将它们转换为无符号整数,然后在ORDER BY子句中使用这些整数进行排序。


SELECT * FROM your_table_name
ORDER BY
  CAST(SUBSTRING_INDEX(version_column_name, '.', 1) AS UNSIGNED),
  CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(version_column_name, '.', 2), '.', -1) AS UNSIGNED),
  CAST(SUBSTRING_INDEX(version_column_name, '.', -1) AS UNSIGNED);

👉 详细解释:

  1. 使用SUBSTRING_INDEX函数提取版本号的主要、次要和补丁部分。
  2. 使用CAST函数将提取到的部分转换为无符号整数。
  3. ORDER BY子句中使用这些整数进行排序。

✅ 执行结果:


SELECT * FROM test_version
ORDER BY
  CAST(SUBSTRING_INDEX(version, '.', 1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(version, '.', -1) AS UNSIGNED) DESC ;

方案三:

使用内置函数SUBSTRING_INDEXCONVERT

💻 解决思路:

与方案二类似,但使用CONVERT函数将提取到的部分转换为无符号整数。

SELECT * FROM your_table_name
ORDER BY
  CONVERT(SUBSTRING_INDEX(version_column_name, '.', 1), UNSIGNED),
  CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(version_column_name, '.', 2), '.', -1), UNSIGNED),
  CONVERT(SUBSTRING_INDEX(version_column_name, '.', -1), UNSIGNED);

👉 详细解释:

  1. 使用SUBSTRING_INDEX函数提取版本号的主要、次要和补丁部分。
  2. 使用CONVERT函数将提取到的部分转换为无符号整数。
  3. ORDER BY子句中使用这些整数进行排序。

✅ 执行结果:

SELECT * FROM test_version
ORDER BY
  CONVERT(SUBSTRING_INDEX(version, '.', 1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(version, '.', -1), UNSIGNED) DESC;

方案四:

使用内置函数INET_ATON(适用于最多三位数的版本号)

💻 解决思路:

将版本号使用INET_ATON函数将其转换为整数,最后在ORDER BY子句中使用这些整数进行排序。

SELECT * FROM your_table_name
ORDER BY INET_ATON(version_column_name);

👉 详细解释:

  1. 使用INET_ATON函数将替换后的版本号转换为整数。
  2. ORDER BY子句中使用这些整数进行排序。

INET_ATON 是 MySQL 中的一个函数,用于将 IPv4 地址转换为无符号整数(32 位)。其名称是 Internet Address to Number 的缩写,表示将 Internet 地址转换为数字。具体来说,INET_ATON 函数接受一个形如 "xxx.xxx.xxx.xxx" 的 IPv4 地址字符串,并返回对应的 32 位无符号整数。

例如,IPv4 地址 "192.168.1.1" 将被转换为整数 3232235777。

使用 INET_ATON 函数有几个方面需要注意:

  1. IPv4 格式要求: INET_ATON 函数要求输入的 IPv4 地址必须符合标准的 IPv4 地址格式,即由四个用点分隔的十进制数字组成,每个数字范围在 0 到 255 之间。
  2. 返回值: INET_ATON 函数返回一个无符号整数,表示输入的 IPv4 地址对应的 32 位整数。如果输入的地址不是有效的 IPv4 地址,则返回 NULL。
  3. 适用范围: 主要用于处理网络相关的数据,例如 IP 地址的存储和比较。在实际应用中,通常用于存储 IP 地址,以便在数据库中进行高效的搜索和排序。
  4. 不适合所有情况: 尽管 INET_ATON 函数可以将字符串形式的 IPv4 地址转换为整数,但它并不适用于所有的数字转换需求

✅ 执行结果:

SELECT *,INET_ATON(version) FROM test_version
ORDER BY INET_ATON(version) DESC;
---------------------------------------------
3.6.1 50724865
3.5.13  50659341
3.5.8 50659336
2.10.1  34209793
2.0.38  33554470
2.0.34  33554466
1.0.12  16777228
1.0.2 16777218
----------------------------------------------

四种方案的查询效率

在MySQL中,查询效率通常取决于多个因素,如索引的使用、数据类型的选择、函数的使用等

  • 🛠️ 方案一:使用自定义排序函数

这种方案的查询效率可能较低,因为它使用了自定义函数。自定义函数在每一行数据上都需要执行,这可能导致性能下降,尤其是在处理大量数据时。此外,自定义函数可能不会使用索引,从而进一步降低查询效率。

  • 🛠️ 方案二和方案三:使用内置函数SUBSTRING_INDEXCASTCONVERT

这两种方案的查询效率可能较高,因为它们使用了内置函数。内置函数通常比自定义函数更高效,因为它们是由MySQL引擎实现的,可以更好地利用底层的优化。此外,这两种方案可以利用列上的索引(如果存在)来提高查询效率。

  • 🛠️ 方案四:使用内置函数INET_ATON(适用于最多三位数的版本号)

这种方案的查询效率可能较高,因为它使用了内置函数。然而,它的适用性受到了版本号位数的限制。此外,INET_ATON函数是用于将IPv4地址转换为整数的函数,虽然它可以用于处理最多三位数的版本号,但这种用法可能会让人困惑。

总之,方案二和方案三的查询效率可能更高,因为它们使用了内置函数。方案一的查询效率可能较低,因为它使用了自定义函数。方案四的查询效率可能取决于版本号的位数,但在某些情况下可能是可接受的。在实际应用中,可能需要根据具体情况和需求来选择最合适的方案。同时,为了提高查询效率,可以考虑将原始的版本号列转换为更易于排序的格式,例如将其存储为整数或使用其他方法进行规范化。

测试效率

使用MySQL的EXPLAIN语句和BENCHMARK函数

EXPLAIN 语句分析:

EXPLAIN语句可以帮助了解查询的执行计划,包括使用的索引、扫描的行数等。可以使用EXPLAIN语句分析每种方案的查询计划,以了解它们在执行过程中的性能差异。

例如:

EXPLAIN SELECT * FROM test_version
ORDER BY VERSION_TO_INT(version) DESC;
EXPLAIN SELECT * FROM test_version
ORDER BY
  CAST(SUBSTRING_INDEX(version, '.', 1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(version, '.', -1) AS UNSIGNED) DESC ;
EXPLAIN SELECT * FROM test_version
ORDER BY
  CONVERT(SUBSTRING_INDEX(version, '.', 1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(version, '.', -1), UNSIGNED) DESC;
EXPLAIN SELECT * FROM test_version
ORDER BY INET_ATON(version) DESC;

在使用MySQL的EXPLAIN语句分析查询计划时,以下关键字是重要的:

➡️ type:表示连接类型。连接类型从最优到最差的顺序是:system > const > eq_ref > ref > range > index > ALL。ALL表示全表扫描,这通常是性能低下的表现。

➡️ possible_keys:表示MySQL可以使用哪些索引来优化查询。这并不意味着MySQL一定会使用这些索引,而只是表示它们是可用的。

➡️ key:表示MySQL实际使用的索引。如果key列为NULL,则表示查询没有使用索引。

➡️ key_len:表示MySQL使用的索引的长度。较长的索引通常意味着更精确的查找,但也可能导致性能下降。

➡️ ref:表示索引的哪个部分被用于查找。如果ref列为const,则表示查询使用了常量值;如果为NULL,则表示查询使用了全表扫描。

➡️ rows:表示MySQL预计要检查的行数。这个值可以帮助了解查询的效率。较低的行数通常意味着更高的效率。

➡️ filtered:表示按表条件筛选的行的百分比。100%表示所有行都符合条件,0%表示没有行符合条件。这个值可以帮助了解查询的效率。

➡️ Extra:包含未列出在其他列中的额外信息。例如,Using temporary表示查询使用了临时表;Using filesort表示查询需要额外的文件排序操作。这些信息可以帮助了解查询的性能瓶颈。

在分析EXPLAIN输出时,应该关注这些关键字,以了解查询的执行计划和性能。根据这些信息,可以优化查询和索引,以提高查询效率。在实际应用中,可能需要根据具体情况调整这些关键字的重要性。同时,为了提高查询效率,可以考虑将原始的版本号列转换为更易于排序的格式,例如将其存储为整数或使用其他方法进行规范化。

BENCHMARK 函数查询:

BENCHMARK函数可以帮助测量查询的执行时间。可以使用BENCHMARK函数运行每种方案的查询多次,以了解它们在实际执行中的性能差异。

例如:

SELECT BENCHMARK(100000000, (SELECT COUNT(*) FROM test_version
ORDER BY VERSION_TO_INT(version) DESC));
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY
  CAST(SUBSTRING_INDEX(version, '.', 1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(version, '.', -1) AS UNSIGNED) DESC ));
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY
  CONVERT(SUBSTRING_INDEX(version, '.', 1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(version, '.', -1), UNSIGNED) DESC ));
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY INET_ATON(version) DESC ));

执行结果:

可以看出,第二种方式和第三种方式执行结果相近,第一种因为使用了临时表的原因,执行效率要差一些;第四种效率上较高,但是具有局限性。

[SQL]SELECT BENCHMARK(100000000, (SELECT COUNT(*) FROM test_version
ORDER BY VERSION_TO_INT(version) DESC));
受影响的行: 0
时间: 2.162s
[SQL]
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY
  CAST(SUBSTRING_INDEX(version, '.', 1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1) AS UNSIGNED) DESC,
  CAST(SUBSTRING_INDEX(version, '.', -1) AS UNSIGNED) DESC ));
受影响的行: 0
时间: 2.110s
[SQL]
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY
  CONVERT(SUBSTRING_INDEX(version, '.', 1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(version, '.', 2), '.', -1), UNSIGNED) DESC,
  CONVERT(SUBSTRING_INDEX(version, '.', -1), UNSIGNED) DESC ));
受影响的行: 0
时间: 2.113s
[SQL]
SELECT BENCHMARK(100000000, ( SELECT COUNT(*) FROM test_version
ORDER BY INET_ATON(version) DESC ));
受影响的行: 0
时间: 2.049s


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
26天前
|
存储 关系型数据库 MySQL
MySQL主从复制原理和使用
本文介绍了MySQL主从复制的基本概念、原理及其实现方法,详细讲解了一主两从的架构设计,以及三种常见的复制模式(全同步、异步、半同步)的特点与适用场景。此外,文章还提供了Spring Boot环境下配置主从复制的具体代码示例,包括数据源配置、上下文切换、路由实现及切面编程等内容,帮助读者理解如何在实际项目中实现数据库的读写分离。
MySQL主从复制原理和使用
|
16天前
|
关系型数据库 MySQL
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
42 5
|
20天前
|
SQL NoSQL 关系型数据库
2024Mysql And Redis基础与进阶操作系列(5)作者——LJS[含MySQL DQL基本查询:select;简单、排序、分组、聚合、分组、分页等详解步骤及常见报错问题所对应的解决方法]
MySQL DQL基本查询:select;简单、排序、分组、聚合、分组、分页、INSERT INTO SELECT / FROM查询结合精例等详解步骤及常见报错问题所对应的解决方法
|
1月前
|
缓存 算法 关系型数据库
Mysql(3)—数据库相关概念及工作原理
数据库是一个以某种有组织的方式存储的数据集合。它通常包括一个或多个不同的主题领域或用途的数据表。
52 5
Mysql(3)—数据库相关概念及工作原理
|
21天前
|
关系型数据库 MySQL
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
28 1
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1636 14
|
26天前
|
SQL 关系型数据库 MySQL
Mysql中搭建主从复制原理和配置
主从复制在数据库管理中广泛应用,主要优点包括提高性能、实现高可用性、数据备份及灾难恢复。通过读写分离、从服务器接管、实时备份和地理分布等机制,有效增强系统的稳定性和数据安全性。主从复制涉及I/O线程和SQL线程,前者负责日志传输,后者负责日志应用,确保数据同步。配置过程中需开启二进制日志、设置唯一服务器ID,并创建复制用户,通过CHANGE MASTER TO命令配置从服务器连接主服务器,实现数据同步。实验部分展示了如何在两台CentOS 7服务器上配置MySQL 5.7主从复制,包括关闭防火墙、配置静态IP、设置域名解析、配置主从服务器、启动复制及验证同步效果。
Mysql中搭建主从复制原理和配置
|
1月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
1月前
|
Java 关系型数据库 MySQL
【编程基础知识】Eclipse连接MySQL 8.0时的JDK版本和驱动问题全解析
本文详细解析了在使用Eclipse连接MySQL 8.0时常见的JDK版本不兼容、驱动类错误和时区设置问题,并提供了清晰的解决方案。通过正确配置JDK版本、选择合适的驱动类和设置时区,确保Java应用能够顺利连接MySQL 8.0。
145 1
|
21天前
|
SQL 关系型数据库 MySQL
MySql5.6版本开启慢SQL功能-本次采用永久生效方式
MySql5.6版本开启慢SQL功能-本次采用永久生效方式
33 0

相关产品

  • 云数据库 RDS MySQL 版