查找 MySQL InnoDB 索引損壞
我的本地主機上有大型 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查詢的截圖:
第三個查詢與第一個查詢不同的是“LIKE”而不是“=”
我不知道第一個查詢返回空集的原因,它看起來像 MySQL 中與時間戳欄位相關的一些錯誤。
我執行查詢,它修復了這個錯誤:
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-zone
my.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 引擎,第一次選擇將返回正確(非空)結果。