MySQL8.0 存儲表的行數和修改日期不自動更新的問題

原文鏈接:https://blog.csdn.net/w892824196/article/details/86543007

之前在問答模塊提了個mysql8.0的問題,困惑了好久,今天無意間刷到了大神的解答,瞬間解決了問題。太牛逼了。

之前的問答鏈接: https://ask.csdn.net/questions/714123

mysql 再升級了8.0後,存儲表在做了修改後,或者升級後,表的行數和修改時間都不會修改,這樣每次都不知道數據是否更新,也不知道實際的行數是多少,很是 頭疼。

近期終於解決了,看來還是要多看開發更新文檔,並從基礎學習起來,才能逐步解決問題。感恩。

 

在MySQL8.0以前,通常會通過infomation_schema的表來獲取一些元數據,例如從tables表中獲取表的下一個auto_increment值,從indexes表獲取索引的相關信息等。

但在MySQL8.0去查詢這些信息的時候,出現了不準確的情況。例如auto_increment,

--此時test表的auto_increment是204
mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=204 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

--從information_schema.tables查出test表的auto_increment是204,這時tables表信息是準確的
mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='test';                                   
+----------------+
| AUTO_INCREMENT |
+----------------+
|            204 |
+----------------+
1 row in set (0.01 sec)
--將test表的auto_increment修改爲300
mysql> alter table test auto_increment=300;
Query OK, 0 rows affected (0.10 sec)
Records: 0  Duplicates: 0  Warnings: 0

--查詢tables表,發現auto_increment仍然是204;在MySQL8.0以前,這時tables表的auto_increment應該是顯示最新值300的
mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            204 |
+----------------+
1 row in set (0.00 sec)

--向test表插入數據,應用最新的auto_increment
mysql> insert into test values();
Query OK, 1 row affected (0.02 sec)

--檢查test表的最大值,確實是300
mysql> select max(id) from test;
+---------+
| max(id) |
+---------+
|     300 |
+---------+
1 row in set (0.00 sec)

--test表插入操作以後,再次查詢tables表,auto_increment值仍然是204
mysql> select auto_increment from information_schema.tables where table_schema='test' and table_name='test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|            204 |
+----------------+
1 row in set (0.00 sec)

又例如一個表的更新時間

--從tables表看到test表上一次更新時間是2018-11-29 09:12:48
mysql> select update_time from information_schema.tables where table_schema='test' and table_name='test';              
+---------------------+
| UPDATE_TIME         |
+---------------------+
| 2018-11-29 09:12:48 |
+---------------------+
1 row in set (0.00 sec)


mysql> select sysdate();
+---------------------+
| sysdate()           |
+---------------------+
| 2018-11-29 09:21:49 |
+---------------------+
1 row in set (0.00 sec)

--對test表插入數據,這時test表的update_time應該是當前時間
mysql> insert into test values();
Query OK, 1 row affected (0.09 sec)

mysql> select sysdate();
+---------------------+
| sysdate()           |
+---------------------+
| 2018-11-29 09:22:02 |
+---------------------+
1 row in set (0.00 sec)

--但從tables表查詢到update_time仍然沒更新
mysql> select update_time from information_schema.tables where table_schema='test' and table_name='test';
+---------------------+
| UPDATE_TIME         |
+---------------------+
| 2018-11-29 09:12:48 |
+---------------------+
1 row in set (0.00 sec)
從以上例子可以看出,MySQL8.0的tables表變得不可靠了。前面文章有說到,MySQL8.0裏,tables不再是某個引擎表,而是改造成了視圖。再仔細看一下tables視圖的定義

select `cat`.`name` AS `TABLE_CATALOG`,`sch`.`name` AS `TABLE_SCHEMA`,`tbl`.`name` AS `TABLE_NAME`,`tbl`.`type` AS `TABLE_TYPE`,if((`tbl`.`type` = 'BASE TABLE'),`tbl`.`engine`,NULL) AS `ENGINE`,
if((`tbl`.`type` = 'VIEW'),NULL,10) AS `VERSION`,`tbl`.`row_format` AS `ROW_FORMAT`,
internal_table_rows(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`table_rows`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `TABLE_ROWS`,
internal_avg_row_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`avg_row_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `AVG_ROW_LENGTH`,
internal_data_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`data_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `DATA_LENGTH`,
internal_max_data_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`max_data_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `MAX_DATA_LENGTH`,
internal_index_length(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`index_length`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `INDEX_LENGTH`,
internal_data_free(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`data_free`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `DATA_FREE`,
internal_auto_increment(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`auto_increment`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0),`tbl`.`se_private_data`) AS `AUTO_INCREMENT`,
`tbl`.`created` AS `CREATE_TIME`,
internal_update_time(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(cast(`stat`.`update_time` as unsigned),0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `UPDATE_TIME`,
internal_check_time(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(cast(`stat`.`check_time` as unsigned),0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `CHECK_TIME`,
`col`.`name` AS `TABLE_COLLATION`,internal_checksum(`sch`.`name`,`tbl`.`name`,if(isnull(`tbl`.`partition_type`),`tbl`.`engine`,''),`tbl`.`se_private_id`,(`tbl`.`hidden` <> 'Visible'),`ts`.`se_private_data`,coalesce(`stat`.`checksum`,0),coalesce(cast(`stat`.`cached_time` as unsigned),0)) AS `CHECKSUM`,
if((`tbl`.`type` = 'VIEW'),NULL,get_dd_create_options(`tbl`.`options`,if((ifnull(`tbl`.`partition_expression`,'NOT_PART_TBL') = 'NOT_PART_TBL'),0,1))) AS `CREATE_OPTIONS`,
internal_get_comment_or_error(`sch`.`name`,`tbl`.`name`,`tbl`.`type`,`tbl`.`options`,`tbl`.`comment`) AS `TABLE_COMMENT` 
from (((((`mysql`.`tables` `tbl` join `mysql`.`schemata` `sch` on((`tbl`.`schema_id` = `sch`.`id`))) 
join `mysql`.`catalogs` `cat` on((`cat`.`id` = `sch`.`catalog_id`))) 
left join `mysql`.`collations` `col` on((`tbl`.`collation_id` = `col`.`id`))) 
left join `mysql`.`tablespaces` `ts` on((`tbl`.`tablespace_id` = `ts`.`id`))) 
left join `mysql`.`table_stats` `stat` on(((`tbl`.`name` = `stat`.`table_name`) and (`sch`.`name` = `stat`.`schema_name`)))) 
where (can_access_table(`sch`.`name`,`tbl`.`name`) and is_visible_dd_object(`tbl`.`hidden`))
可以看到,auto_increment和update_time列均引用自mysql.table_stats表。那麼tables視圖的信息不準確,根本原因就是table_stats表的統計信息並沒有實時更新。

解決統計信息過舊的問題,從以往的經驗來看,當表數據更新佔比達到一定數值,就會觸發統計信息收集。所以嘗試了不斷插入和更新test表,但tables視圖的信息仍然是不準確的,也就說明table_stats的統計信息根本沒有更新。

終極的手段當然是使用analyze table命令去人爲的觸發表信息收集,tables視圖的信息會更新至當前準確的狀態。

但如果總是要analyze table命令去人爲更新才能得到真實的數據,那麼tables表存在的意義何在?

對此,做一番研究。

原來在MySQL8.0,數據字典方面做了不少的改動。本文就不詳細介紹所有的知識點,後續文章中再講述。針對tables視圖等不準確的情況,其實是跟數據字典表和其數據緩存有關係。

數據字典有很多相關的表,但這些表是不可見的。既不能通過select來獲取表數據,也不能通過show tables看到它的蹤影,同樣也不會出現在information_schema.tables的table_name範疇裏。例如跟庫表有關的數據字典tables(注意,此tables跟information_schema.tables並不是同一個東西),在歸屬於mysql庫下,但就算有火眼金睛也不能讓它顯形:

mysql> use mysql;
Database changed
mysql> show tables like 'table';
Empty set (0.01 sec)

但是,大部分數據字典表會有相關的視圖來獲取它的數據,例如tables表相關的視圖是information_schema.tables,當然,從information_schema.tables的定義看,也不是一對一的關係,其中還包含其他表的數據。

數據字典表用來做什麼呢,還記得.frm,db.opt這些文件嗎?在MySQL8.0裏,你會發現這些文件都沒有了。原本記錄在這些文件中的元數據,現在記錄就記錄在數據字典表裏,而數據字典表集中存在一個單獨的innodb表空間中,系統文件名爲mysql.ibd,也就是說,元數據不再是直接在.frm等文件上讀寫,而是存在存儲引擎上。

爲了最小化磁盤IO,MySQL8.0增加了一個字典對象緩存(dictionary object cache)。同時爲了提高information_schema的查詢效率,statistics和tables字典表的數據緩存在字典對象緩存中,並且有一定的保留時間,如果沒超過保留時間,即使是實例重啓,緩存中的信息也不會更新,只有超過了保留時間,纔會到存儲引擎裏抓取最新的數據。同時,字典對象緩存採用LRU的方式來管理緩存空間。

那麼到這裏,information_schema.tables視圖不準確的疑問就解開了,原因即是字典對象緩存中統計信息並沒有更新,那麼怎麼解決呢?可以通過設置information_schema_stats_expiry爲0來使字典對象緩存實時更新,該參數默認值爲86400,即24小時。

問題解決了,那麼來捋一捋,都有哪些情況下,字典緩存中索引和表的統計信息不會自動更新呢?
1.緩存中統計信息還沒過期;
2.information_schema_stats_expiry沒設成0;
3.當實例在read_only相關模式下運行;
4.當查詢同時獲取performance schema的數據。

針對第一二點,可以通過設置set global information_schema_stats_expiry=0來解決,也可以僅在會話級設置;針對以上問題,除了第三點,都可以通過analyze table來解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章