我正在使用 MySQL 5.7。我创建了一个表,其中包含一个 DATETIME 类型的虚拟列(未存储),上面有一个索引。当我处理它时,我注意到 order by 没有返回所有数据(我期望在顶部的一些数据丢失了)。 MAX 和 MIN 的结果也是错误的。 跑完后
ANALYZE TABLE
CHECK TABLE
OPTIMIZE TABLE
那么结果是正确的。我猜索引数据有问题,所以我有几个问题:
- 何时以及为什么会发生这种情况?
- 有什么办法可以避免这种情况吗?
- 在我运行的 3 个命令中,哪个是正确使用的?
我担心将来会发生这种情况,但我不会注意到。
编辑:
根据评论中的要求,我添加了表格定义:
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;
谢谢
最佳答案
长篇小说;
问题在于您的数据来自通过索引具体化的虚拟列。您正在执行的检查、优化、分析操作会强制同步索引并修复任何错误。从今以后,这将为您提供正确的结果。至少在索引再次不同步之前。
为什么会发生
很多问题都是由您的表设计问题引起的。让我们开始吧。
`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 是另一个虚拟列。这会产生一些影响。
When a secondary index is created on a generated virtual column, generated column values are materialized in the records of the index. If the index is a covering index (one that includes all the columns retrieved by a query), generated column values are retrieved from materialized values in the index structure instead of computed “on the fly”.
这与
相反VIRTUAL: Column values are not stored, but are evaluated when rows are read, immediately after any BEFORE triggers. A virtual column takes no storage.
引用: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
。
这使您的表格更整洁,查询速度可能更快
关于MySQL SELECT 返回错误结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38543749/