MySQL SELECT 返回错误结果

Posted

技术标签:

【中文标题】MySQL SELECT 返回错误结果【英文标题】:MySQL SELECT return wrong results 【发布时间】:2016-07-23 16:06:11 【问题描述】:

我正在使用 mysql 5.7。我创建了一个带有 DATETIME 类型的虚拟列(未存储)的表,上面有一个索引。当我在处理它时,我注意到 order by 并没有返回所有数据(我期望在顶部的一些数据丢失了)。 MAX 和 MIN 的结果也是错误的。 跑完之后

ANALYZE TABLE 
CHECK TABLE
OPTIMIZE TABLE

那么结果是正确的。我猜索引数据有问题,所以我有几个问题:

    何时以及为何会发生这种情况? 有没有办法防止这种情况发生? 在我运行的 3 个命令中,哪个是正确的?

我担心将来会发生这种情况,但我不会注意到。

编辑

根据 cmets 的要求,我添加了表定义:

CREATE TABLE `items` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `image` json DEFAULT NULL,
  `status` json DEFAULT NULL,
  `status_expired` tinyint(1) GENERATED ALWAYS AS (ifnull(json_contains(`status`,'true','$.expired'),false)) VIRTUAL COMMENT 'used for index: it checks if status contains expired=true',
  `lifetime` tinyint(4) NOT NULL,
  `expiration` datetime GENERATED ALWAYS AS ((`create_date` + interval `lifetime` day)) VIRTUAL,
  `last_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `expiration` (`status_expired`,`expiration`) USING BTREE,
  CONSTRAINT `ts_competition_item_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `ts_user_core` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1312459 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED

返回错误结果的查询:

SELECT * FROM items ORDER BY expiration DESC;
SELECT max(expiration),min(expiration) FROM items;

谢谢

【问题讨论】:

显示您的架构、示例数据和预期结果 有人在玩火:dev.mysql.com/doc/refman/5.7/en/… 你为什么这么说? 请提供SHOW CREATE TABLE 和返回错误值的查询。我们需要查看索引、引擎、特定的“虚拟”声明等。 在虚拟列上创建复合键而不是两个常规二级索引是否有特定原因? 【参考方案1】:

TLDR;

问题在于您的数据来自通过索引实现的虚拟列。您正在执行的检查、优化、分析操作会强制同步索引并修复任何错误。从此以后,您将获得正确的结果。至少在索引再次不同步之前。

为什么会发生

大部分问题是由您的餐桌设计问题引起的。让我们开始吧。

`status_expired` tinyint(1) GENERATED ALWAYS AS (ifnull(json_contains(`status`,'true','$.expired'),false)) VIRTUAL

毫无疑问,这是为了克服不能直接索引 mysql 中的 JSON 列的事实。您已经创建了一个虚拟列并对其进行了索引。一切都很好,但是这一列只能包含两个值之一; truefalse。这意味着它的节奏很差。因此,mysql 不太可能将这个索引用于任何事情。

但是我们可以看到您在创建索引时已将status_expired 列与expired 列组合在一起。也许是为了克服上面提到的这种糟糕的基数。但是等等……

`expiration` datetime GENERATED ALWAYS AS ((`create_date` + interval `lifetime` day)) VIRTUAL,

Expiration 是另一个虚拟列。这有一些影响。

在生成的虚拟列上创建二级索引时, 生成的列值在索引的记录中实现。 如果索引是覆盖索引(包含所有列的索引) 由查询检索),生成的列值检索自 索引结构中的物化值,而不是“在 飞”。

参考:https://dev.mysql.com/doc/refman/5.7/en/create-table-secondary-indexes.html#json-column-indirect-index

这不符合

VIRTUAL:不存储列值,而是在行被存储时评估 在任何 BEFORE 触发器之后立即读取。一个虚拟列不需要 存储。

参考:https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html

我们基于合理原则创建虚拟列,即不应存储对列的简单操作生成的值以避免冗余,但通过在其上创建索引,我们重新引入了冗余。

建议的修复

根据所提供的信息,您似乎并不真的需要 status_expired 列,甚至 expired 列。过期的商品已过期!

CREATE TABLE `items` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `image` json DEFAULT NULL,
  `status` json DEFAULT NULL,
  `expire_date` datetime GENERATED ALWAYS AS ((`create_date` + interval `lifetime` day)) VIRTUAL,
  `last_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `expiration` (`expired_date`) USING BTREE,
  CONSTRAINT `ts_competition_item_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `ts_user_core` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1312459 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED

当您需要找出哪些项目已过期时,只需将当前日期与上表中的 expired_date 列进行比较即可。这里的区别在于 expired 不是每个查询中的计算项,而是在创建记录时计算一次 expiry_date

这使您的表格更整洁,查询速度可能更快

【讨论】:

非常感谢,但这实际上并不能解释为什么会发生索引损坏。几点注意事项: 1. 索引虚拟列是 MySQL doc 的建议,这是一个很好的做法 2. status_expired 用于加速不同的查询(抱歉我不能分享所有细节,所以你不知道) 3.任何虚拟列都可以用应用程序逻辑替换,但它们很有用,这就是我使用过期作为虚拟列的原因

以上是关于MySQL SELECT 返回错误结果的主要内容,如果未能解决你的问题,请参考以下文章

试图制作返回 mysql 'SELECT * FROM' 结果的函数,最终返回 undefined

mySQL select í 返回带有 i 的结果

为啥 MySQL View 和同一个 View 的底层 SELECT 查询返回不同的结果?

存储过程中的 MySQL select 语句返回的结果与 proc 外部不同

mysql返回不正确的bigint结果,非常奇怪的错误

Mysql注入 -- 联合注入