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
列的事实。您已经创建了一个虚拟列并对其进行了索引。一切都很好,但是这一列只能包含两个值之一; true
或 false
。这意味着它的节奏很差。因此,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 View 和同一个 View 的底层 SELECT 查询返回不同的结果?