Mysql

查找 MySQL InnoDB 索引損壞

  • March 15, 2022

我的本地主機上有大型 MySQL InnoDB 表。在這個表中,主鍵是,欄位,,,,上id設置唯一索引。a``b``c

當我執行查詢時:

SELECT * FROM mytable WHERE a = 1 AND b = 2 AND c = 3;

結果是空集,但表中肯定存在這樣的行。當我在末尾添加“ AND id = 1234”時(其中 1234 是預期結果的 id):

SELECT * FROM mytable WHERE a = 1 AND b = 2 AND c = 3 AND id = 1234;

正如預期的那樣,它返回 1 行。

唯一的解釋是唯一索引已損壞。

但是當我嘗試檢查表時:

mysqlcheck -c mydb mytable

或與:

ANALYZE table mydb.mytable;

兩項檢查都返回正常,沒有錯誤。

所以問題是:

如何辨識索引中的此錯誤?有沒有辦法通過一些查詢找到這個錯誤?

MySQL 版本是 5.7.23,在 error.log 中沒有錯誤。


編輯:

事實證明,在創建具有相同結構的空表 mytable1 並通過查詢填充數據之後:

INSERT INTO mytable1 (SELECT * FROM mytable);

mytable1 中存在此錯誤,在索引損壞的情況下這是不可能的。

經過一些研究,我發現問題出在具有timestamp類型的欄位 c 中。

實際的表結構是:

CREATE TABLE `mytable` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `a` int(10) unsigned NOT NULL,
 `b` int(10) unsigned NOT NULL,
 `c` timestamp NOT NULL DEFAULT '1999-01-01 00:00:00',
 `d` tinyint(1) NOT NULL DEFAULT '0',
 `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `index2` (`a`,`b`,`c`,`d`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

有mysql查詢的截圖:

截圖1

第三個查詢與第一個查詢不同的是“LIKE”而不是“=”

我不知道第一個查詢返回空集的原因,它看起來像 MySQL 中與時間戳欄位相關的一些錯誤。

我執行查詢,它修復了這個錯誤:

截圖2

UPDATE mytable SET c = FROM_UNIXTIME(UNIX_TIMESTAMP(c));

此更新後,問題查詢開始正常工作。但實際上,此更新必須什麼都不做。


編輯 2

我發現,這個問題的原因是 2014 年 10 月 26 日俄羅斯的時鐘變化。

因為我的本地設置是:

mysql> select @@global.time_zone, @@session.time_zone;
SYSTEM | SYSTEM

所以在我的 localhost datetime ‘2014-10-26 01:00:00’ 上有兩個時間戳:

mysql> select from_unixtime(1414270800);
2014-10-26 01:00:00

mysql> select from_unixtime(1414274400);
2014-10-26 01:00:00

所以按值選擇返回空集:

mysql> select * from test_db.mytable where c = '2014-10-26 01:00:00';
Empty set

但按 id 選擇返回正確的數據:

mysql> select c from test_db.mytable where id = 316572297;
2014-10-26 01:00:00

我已將default-time-zonemy.cnf 更改為“+03:00”,一切開始正常。

注意:要重現錯誤,您還必須在欄位 c 上有索引。沒有它,按值選擇 c = … 工作正常。

2014 年 10 月 26 日,隨著法律的另一次修改,該國大部分地區的時鐘向後移動了一小時,但並未重新引入夏季夏令時;莫斯科時間永久返回 UTC+03:00。

https://en.wikipedia.org/wiki/Time_in_Russia

聽起來您的系統(作業系統和/或 MySQL)需要更新 TZ 表。

打倒夏令時!

我可以添加步驟來為我的系統(Ubuntu 18.04.1 LTS)重現此錯誤。

例如,在美國,下一次夏令時更改為 1 小時將是 2018 年 11 月 4 日凌晨 2:00(當地時間)。所以我將使用時間戳 1541322000,即美國/太平洋時區的 2018-11-04 01:00:00。

在測試數據庫上創建具有結構的表:

CREATE TABLE `mytable` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `t` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `index2` (`t`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

將會話時區更改為 UTC:

mysql> set session time_zone = 'UTC';
Query OK, 0 rows affected

如果您收到“未知或不正確的時區”之類的錯誤,請首先添加此時區(來自此答案):

$ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -p123 mysql

在表格中插入一行:

mysql> insert into mytable (t) values (from_unixtime(1541322000));
Query OK, 1 row affected

將時區切換到美國/太平洋(在我的情況下,我將表從具有 UTC 時區的外部伺服器轉儲到具有歐洲/莫斯科時區的本地主機):

mysql> set session time_zone = 'US/Pacific';
Query OK, 0 rows affected

然後執行兩個查詢:

mysql> select t from mytable where t = '2018-11-04 01:00:00';
Empty set

mysql> select t from mytable where t = '2018-11-04 01:00:00' and id = 1;
2018-11-04 01:00:00

這兩個查詢的結果看起來不兼容。首先選擇必須返回非空結果。

請注意,如果您從欄位 t 中刪除索引或使用 MyISAM 引擎,第一次選擇將返回正確(非空)結果。

引用自:https://dba.stackexchange.com/questions/220342