【问题标题】:MySQL 5.7 wrong results on sort / subqueryMySQL 5.7 排序/子查询的错误结果
【发布时间】:2016-08-19 16:44:14
【问题描述】:

在 MySQL 5.7(特别是 5.7.13-0ubuntu0.16.04.2)中出现非常奇怪的结果。

我怀疑这可能是 MySQL 中的错误。

DROP TABLE IF EXISTS `test_grids_1`;
CREATE TABLE `test_grids_1` (
  `unq_id` int(11) NOT NULL DEFAULT '0',
  `var_fld` int(11) DEFAULT '0'
) ENGINE=InnoDB;
INSERT INTO `test_grids_1` VALUES
  (1,4500),
  (2,6000);

DROP TABLE IF EXISTS `test_grid_dtl_1`;
CREATE TABLE `test_grid_dtl_1` (
  `dtl_id` int(11) NOT NULL DEFAULT '0',
  `unq_id` int(11) DEFAULT '0',
  `dtl_var` decimal(14,2) DEFAULT '0.00'
) ENGINE=InnoDB;
INSERT INTO `test_grid_dtl_1` VALUES
  (1,1,2.00),
  (2,1,2.40),
  (3,2,2.30);


SELECT
  ( g.calc_var * d.dtl_var ) new_var,
  g.calc_var
FROM
  (
  SELECT
    unq_id,
    IF ( var_fld  > 5000, ( 1 / var_fld ) , 5000 ) calc_var
  FROM
    test_grids_1
  ) g
INNER JOIN
  test_grid_dtl_1 d
    ON d.unq_id = g.unq_id;

+--------------+----------+
| new_var      | calc_var |
+--------------+----------+
| 10000.000000 |     5000 |
| 12000.000000 |     5000 |
|     0.000383 |   0.0002 |
+--------------+----------+

SELECT
  ( g.calc_var * d.dtl_var ) new_var,
  g.calc_var
FROM
  (
  SELECT
    unq_id,
    IF ( var_fld  > 5000, ( 1 / var_fld ) , 5000 ) calc_var
  FROM
    test_grids_1
  ) g
INNER JOIN
  test_grid_dtl_1 d
    ON d.unq_id = g.unq_id
ORDER BY
  1;

+--------------+----------+
| new_var      | calc_var |
+--------------+----------+
|     0.000383 |   0.0002 |
| 10000.000000 |  99.9999 |
| 12000.000000 |  99.9999 |
+--------------+----------+
3 rows in set (0.00 sec)

当包含排序时,它会导致某些条件的返回值完全不正确。

预期为 5000 的值突然变为 99.9999。

如果有人可以检查并确认其他 5.7 安装的类似行为,那就太好了。

谢谢

【问题讨论】:

  • 什么是操作系统和版本? MySQL 5.7 的版本是多少(使用SHOW VARIABLES LIKE "%version%" 获取版本信息)?
  • MySQL:5.7(特别是 5.7.13-0ubuntu0.16.04.2)。" 操作系统:Ubuntu 16.04 LTS
  • 如果我这个周末要安装一个带有 MySQL 的 16.04LTS,我会尝试复制这个问题。

标签: sorting mysql-5.7


【解决方案1】:

发生了什么事?

查询被 MySQL 隐式转换和加入和排序所位。

解决方案

让我们先解决问题,然后讨论我们是如何做到的。注意1/var_fld1.0/var_fld50005000.0 的变化。

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1.0/var_fld, 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+
| new_var       | calc_var   |
+---------------+------------+
|     0.0003833 |    0.00017 |
| 10000.0000000 | 5000.00000 |
| 12000.0000000 | 5000.00000 |
+---------------+------------+

您也可以使用cast 以稍微不同的方式重新编写查询。请注意,我在最后一列中包含了十六进制值。当您继续阅读时,它将很有用:

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, cast(1/var_fld as decimal(15,5)), 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+-----------+
| new_var       | calc_var   | hcalc_var |
+---------------+------------+-----------+
|     0.0003910 |    0.00017 | 0         |
| 10000.0000000 | 5000.00000 | 1388      |
| 12000.0000000 | 5000.00000 | 1388      |
+---------------+------------+-----------+

其他解决方案

注意if 替换为case 语句。

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, case when var_fld > 5000 then 1/var_fld else 5000 end calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+--------------+-----------+
| new_var      | calc_var  |
+--------------+-----------+
|     0.000383 |    0.0002 |
| 10000.000000 | 5000.0000 |
| 12000.000000 | 5000.0000 |
+--------------+-----------+

请注意case 语句不需要任何类型的转换即可获得几乎相同的结果。但是,要获得与第一个查询完全相同的结果,您必须执行以下操作 -

又一个

注意 1.0/var_fld5000.0 以及 cast 而不是 if

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var
FROM (
  SELECT unq_id, case when var_fld > 5000 then 1.0/var_fld else 5000.0 end calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

这是如何突出的?

让我们看一下原始查询;我添加了一个新字段hex(g.calc_var),它是g.calc_var 的十六进制表示。

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1/var_fld, 5000) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+--------------+----------+-----------+
| new_var      | calc_var | hcalc_var |
+--------------+----------+-----------+
|     0.000383 |   0.0002 | 0         |
| 10000.000000 |  99.9999 | 1388      |
| 12000.000000 |  99.9999 | 1388      |
+--------------+----------+-----------+

将结果与解决方案部分中的第一个查询进行比较

SELECT g.calc_var * d.dtl_var as new_var, g.calc_var, hex(g.calc_var) as hcalc_var
FROM (
  SELECT unq_id, IF (var_fld > 5000, 1.0/var_fld, 5000.0) calc_var
  FROM test_grids_1
) g
INNER JOIN test_grid_dtl_1 d ON d.unq_id = g.unq_id
ORDER BY 1

+---------------+------------+-----------+
| new_var       | calc_var   | hcalc_var |
+---------------+------------+-----------+
|     0.0003833 |    0.00017 | 0         |
| 10000.0000000 | 5000.00000 | 1388      |
| 12000.0000000 | 5000.00000 | 1388      |
+---------------+------------+-----------+

请注意,两个查询中的十六进制值完全相同,但十进制值不同。

5000 怎么会变成 99.9999?

select cast(5000 as decimal(6,4)) as test;
+---------+
| test    |
+---------+
| 99.9999 |
+---------+
1 row in set, 1 warning (0.00 sec)

show warnings;
+---------+------+-----------------------------------------------+
| Level   | Code | Message                                       |
+---------+------+-----------------------------------------------+
| Warning | 1264 | Out of range value for column 'test' at row 1 |
+---------+------+-----------------------------------------------+

这样!当 5000 转换为长度为 6 的小数时,包括 4 个小数,结果是适合小数 (6,4) 的最大值。哎哟。

在这种情况下会引发警告,这很好。在测试过程中可能会发现它。但是,有问题的查询不会引发任何警告。这不好。

这会导致多个问题

  • 为什么在没有order by 的情况下可以正确进行投射?
  • order by 中的什么导致了我们注意到的投射方式?
  • 为什么case...end 显示出比if(...) 更好的结果,即使没有进行强制转换?
  • 为什么在完成这样的投射时没有发出警告?

您可能希望向 MySQL 人员提交错误报告。 我没有安装最新的 MariaDB,所以我不能说 MariaDB 中是否也存在这个问题。我很好奇并安装了 10.0.25-MariaDB-0ubuntu0.16.04.1。 MariaDB 10.0.25 中似乎也存在同样的问题。

这可以预防吗?

是的。每当处理 int 到 float/double/decimal 时,隐式转换以获得可预测的结果。我仍然希望 MySQL 能够研究这种极端情况。如果有人遇到解释此行为的文档,请在此答案中添加评论,以便我进行自我教育。

【讨论】:

  • 超级响应@zedfocus !!!感谢您非常有见地的回答,我同意我们需要通知 MySQL。我会向他们提出错误报告。
  • 令我难以置信的是,让我感到BUG的事实是,当结果集不是时,这不会发生(在我的原始示例中) b> 排序。即使 所有值 应该是 5000,它们仍然被“强制转换”以在排序时得到 99.999。
  • 是的,你是对的。这似乎是一个错误。这是一种很难解释的行为。
  • 如果你最终在 MySQL/MariaDB 的问题跟踪器上提出了问题,你能把链接放在这里吗?我很想关注这个问题,看看根本原因。如果您希望我输入错误,我当然可以这样做
  • 优秀。感谢您提交错误报告。如果上面的答案有助于解决您的紧迫问题,请随时将其标记为已接受并结束您的问题,或者随时等待更多答案。
猜你喜欢
  • 1970-01-01
  • 2018-09-26
  • 2017-07-25
  • 1970-01-01
  • 2016-08-21
  • 2019-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多