MySQL SELECT返回错误的结果

时间:2016-07-23 16:06:11

标签: mysql sql select virtual-column

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

ANALYZE TABLE 
CHECK TABLE
OPTIMIZE TABLE

然后结果是正确的。我想索引数据存在问题,所以我几乎没有问题:

  1. 何时以及为何会发生这种情况?
  2. 有办法防止这种情况吗?
  3. 在我运行的3个命令中,哪个是正确使用的?
  4. 我担心将来会发生这种情况,但我不会注意到。

    修改

    根据评论中的要求添加了表格定义:

    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;
    

    由于

1 个答案:

答案 0 :(得分:9)

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,

到期是另一个虚拟列。这会产生一些影响。

  

在生成的虚拟列上创建辅助索引时,   生成的列值在索引的记录中具体化。   如果索引是覆盖索引(包含所有列的索引)   通过查询检索),从中检索生成的列值   索引结构中的具体化值而不是“在...上计算”   飞”。

参考: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一次。

这使你的桌子更整洁,查询可能更快