GA

2026/10/22

MySQL 26.10.0で一番好きな機能、Health Monitor

TL;DR


MySQL 26.10.0で 商用版MySQL MySQL HeatWave(クラウド版)からバックポートされた機能。
インストール自体は簡単。

mysql2610 10> INSTALL COMPONENT 'file://component_health_monitor';
Query OK, 0 rows affected (0.402 sec)

これだけで 日々の覚書: HeatWave MySQLがどれくらいVanilla MySQL GPL版と同じくらいか考える旅 / performance_schema編 に列挙していたようなモニター系のテーブルが有効になる。

設定できそうなパラメータ群はこれ。

mysql2610 11> SELECT variable_name, SUBSTR(variable_value, 1, 15) FROM performance_schema.global_variables WHERE variable_name LIKE 'health%';
+----------------------------------------------+-------------------------------+
| variable_name                                | SUBSTR(variable_value, 1, 15) |
+----------------------------------------------+-------------------------------+
| health_monitor.disk_fallback_enable          | OFF                           |
| health_monitor.disk_fallback_force           | OFF                           |
| health_monitor.disk_forecast_report_interval | 240                           |
| health_monitor.disk_forecast_run_mode        | NORMAL                        |
| health_monitor.disk_forecast_running         | ON                            |
| health_monitor.disk_forecast_sample_interval | 60                            |
| health_monitor.disk_forecast_test_filename   |                               |
| health_monitor.disk_low_space_critical_level | 2000                          |
| health_monitor.disk_low_space_duration       | 300                           |
| health_monitor.disk_low_space_level          | 5000                          |
| health_monitor.disk_low_space_percent        | 4                             |
| health_monitor.disk_monitored                |                               |
| health_monitor.disk_recovery_enable          | ON                            |
| health_monitor.disk_recovery_increment       | 300                           |
| health_monitor.disk_recovery_level           | 5                             |
| health_monitor.disk_recovery_max_cycles      | 3                             |
| health_monitor.disk_recovery_time_1          | 300                           |
| health_monitor.disk_recovery_time_2          | 300                           |
| health_monitor.disk_recovery_window          | 86400                         |
| health_monitor.disk_retention                | 86400                         |
| health_monitor.disk_running                  | ON                            |
| health_monitor.disk_sample_rate              | 60                            |
| health_monitor.disk_usage_warning_level_1    | 20                            |
| health_monitor.disk_usage_warning_level_2    | 10                            |
| health_monitor.disk_usage_warning_level_3    | 5                             |
| health_monitor.memory_report_definition      | [{  "title" : "               |
| health_monitor.memory_reporting              | ON                            |
| health_monitor.memory_retention              | 86400                         |
| health_monitor.memory_running                | ON                            |
| health_monitor.memory_sample_rate            | 60                            |
| health_monitor.memory_usage_warning_level_1  | 1024                          |
| health_monitor.memory_usage_warning_level_2  | 500                           |
| health_monitor.memory_usage_warning_level_3  | 100                           |
| health_monitor.periodic_report_definition    | [{   "title" :                |
| health_monitor.periodic_reporting            | OFF                           |
| health_monitor.status_interval               | 10                            |
| health_monitor.system_cpu_stats_history      | ON                            |
| health_monitor.system_disk_stats_history     | ON                            |
| health_monitor.system_memory_stats_history   | ON                            |
| health_monitor.system_process_stats_history  | ON                            |
| health_monitor.system_retention              | 86400                         |
| health_monitor.system_running                | ON                            |
| health_monitor.system_sample_rate            | 60                            |
+----------------------------------------------+-------------------------------+
43 rows in set (0.001 sec)

中でも凶悪(?) なのが health_monitor.disk_fallback_enable で、デフォルトはもちろんOFFだけどDiskの残量が指定した閾値を下回ると強制的に SET GLOBAL offline_mode = ON, super_read_only = ON が走るところ。

( ´-`).oO(迂闊にこのへんの権限をいじるアカウントが無いと、Diskを拡張するしかなくて、MySQL HeatWaveだったらまだしもオンプレでこれが着火したらそれは落ちているのとほぼ一緒なのでは…

ドキュメント にももちろんあるけど、日本語話者なら(英語版もあるだろうけど) Oracle CloudのMySQL HeatWaveのヘルスモニターに関する記述 が「何が起こるのか何を起こせるのか」がまとまっていて読みやすい。

グラフはPMMとかで見ればいいけど、やっぱりSQLでもゴニョりたいとかそういう人向け。
なおヘルスモニターの出力は log_error_verbosity が3ならエラーログにも吐いてくれる。当然 performance_schema.error_log からSELECTもできる(5MB以内なら)

2026-10-22T03:14:17.330992-00:00 10 [Note] [MY-013694] [Health] Health Monitor: Registered with option usage tracker
2026-10-22T03:14:17.331004-00:00 10 [Note] [MY-013694] [Health] Health Monitor: Started
2026-10-22T03:14:27.000393-00:00 0 [Note] [MY-013694] [Health] Disk Collector: New Volume Detected (499.8G): running=ON, volume monitored='/usr/mysql/26.10.0/data/', base interval=10s, sample interval=60s, retention=86400s, max samples=1440, status interval=10, low level=5000 MiB, low percent=4%%, critical level=2000 MiB, warning levels=20/10/5%%, fallback (enable=OFF, force=OFF), low space duration=300s, recovery (enable=ON, level=5%%, time 1=300s, time 2=300s, max cycles=3, increment=300, window=86400)
2026-10-22T03:14:27.000447-00:00 0 [Warning] [MY-013695] [Health] Disk: Warning Level 1 (103.9G): volume='/usr/mysql/26.10.0/data/', avail=94.3G, total=499.8G, used=81.1%%, reserve=4.9G, critical=2.0G, recovery=29.6G, warnings=103.9G/54.4G/29.6G
2026-10-22T03:14:37.000345-00:00 0 [Warning] [MY-013695] [Health] Disk: Warning Level 1 (103.9G): volume='/usr/mysql/26.10.0/data/', avail=94.3G, total=499.8G, used=81.1%%, reserve=4.9G, critical=2.0G, recovery=29.6G, warnings=103.9G/54.4G/29.6G
2026-10-22T03:15:17.000448-00:00 0 [Note] [MY-013694] [Health] Memory: avail=13.3G, total=15.3G, used=13.1%%, mysqld=381M, warnings=1.0G/500M/100M
2026-10-22T03:15:27.000124-00:00 0 [Note] [MY-013694] [Health] Disk: Not enough data points for forecasting: Needed: 2880 Current: 1: running=ON, volume='/usr/mysql/26.10.0/data/', used=405.5G, total=499.8G, used=81.1%%, metrics=1, last forecast=n/a
2026-10-22T03:24:17.000446-00:00 0 [Note] [MY-013694] [Health] Memory: avail=13.3G, total=15.3G, used=13.0%%, mysqld=388M, warnings=1.0G/500M/100M
2026-10-22T03:24:17.000453-00:00 0 [Warning] [MY-013695] [Health] Disk: Warning Level 1 (103.9G): volume='/usr/mysql/26.10.0/data/', avail=94.3G, total=499.8G, used=81.1%%, reserve=4.9G, critical=2.0G, recovery=29.6G, warnings=103.9G/54.4G/29.6G

久々に楽しい。新機能(?) でした。俺はこれ好き。

​

2026/10/20

caching_sha2_passwordのauthentication_stringを計算してもらう

TL;DR


mysql84 149071> CREATE USER yoku0825 IDENTIFIED WITH caching_sha2_password BY 'test';
Query OK, 0 rows affected (0.00 sec)

mysql84 149071> CREATE USER yoku0826 IDENTIFIED WITH caching_sha2_password BY 'test';
Query OK, 0 rows affected (0.01 sec)

mysql84 149071> SELECT user, host, authentication_string FROM mysql.user WHERE user LIKE 'yoku%';  -- 制御文字が入ることがあって表示が変になることあるある
+----------+------+------------------------------------------------------------------------+
| user     | host | authentication_string                                                  |
+----------+------+------------------------------------------------------------------------+
| yoku0825 | %    | $A$005$2thQcC;,Av1@ IY~BihHGrWskcdpi.zGu1kMoFuqtQJ9eMjyRCkaRos93557 |
| yoku0826 | %    | $A$005Hv%b6
.&N%-JBo^3uqAgcrXEygcbJVXEdUorl/y4RVg0JEte08LGQmVJi2 |
+----------+------+------------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql84 149071> SELECT user, host, HEX(authentication_string) FROM mysql.user WHERE user LIKE 'yoku%';  -- HEXで取ると安全
+----------+------+----------------------------------------------------------------------------------------------------------------------------------------------+
| user     | host | HEX(authentication_string)                                                                                                                   |
+----------+------+----------------------------------------------------------------------------------------------------------------------------------------------+
| yoku0825 | %    | 24412430303524327468105163433B2C41763140010949591C7E42696848477257736B636470692E7A4775316B4D6F46757174514A39654D6A7952436B61526F733933353537 |
| yoku0826 | %    | 24412430303524084876031A256201360C2E264E252D4A426F155E337571416763725845796763624A56584564556F726C2F7934525667304A45746530384C47516D564A6932 |
+----------+------+----------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

生の authentication_string の $ はデリミタで、 $A$005$ が SHA256ダイジェストで5000回ストレッチ を表すらしい ( ITERATION_MULTIPLIER は9.7.2現在 1000で固定 )。
その後、 SALT_LENGTH = 20 バイトのソルトと STORED_SHA256_DIGEST_LENGTH = 43 バイトのダイジェストがデリミタなしでくっつく。

認証としては「同じソルトで同じ回数ストレッチしたら同じダイジェストにたどり着く」だけで十分なので、「一度作ったアカウントのauthentication_stringはパスワードが同じなら使い回せる」のは mysql_native_password の時と同じ。

mysql84 149226> CREATE USER yoku0827 IDENTIFIED WITH caching_sha2_password AS x'24412430303524084876031A256201360C2E264E252D4A426F155E337571416763725845796763624A56584564556F726C2F7934525667304A45746530384C47516D564A6932'; -- yoku0826のauthentication_stringをHEXにしたもの。 x'..' は UNHEX('..') と同じだけどUNHEX関数を噛ませると構文的にエラーになるので x'..' (これはリテラル扱い) で食わせる
Query OK, 0 rows affected (0.01 sec)

$ mysql84 -uyoku0827 -ptest -e "SHOW GRANTS"
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------------------------+
| Grants for yoku0827@%                |
+--------------------------------------+
| GRANT USAGE ON *.* TO `yoku0827`@`%` |
+--------------------------------------+

なのでソルトを自分で指定すればこの43バイトダイジェストの部分作れるのでは? と思ってはいたものの、 Caching_sha2_password::generate_crypt5 からの my_crypt_genhash を見てもopensslをちゃんと使わずに手元で計算するの無理だと思っていたが、世はAI時代でPythonの形で出してもらった。便利な世の中になったものだ…。

Kimi-K2.7-Codeに書いてもらったソルト固定でcaching_sha2_passwordの値を計算してもらうスクリプト


余談。

たまに驚かれるけれど、 mysql_native_password のハッシュは一般的なLinuxのコマンドだけでも作れたりする。

$ echo -n "mypass" | sha1sum | awk '{print $1}' | xxd -r -p | sha1sum | awk '{print toupper("*"$1)}'
*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4
  
$ mysql -h 172.17.0.6 -e "SELECT PASSWORD('mypass')"
+-------------------------------------------+
| PASSWORD('mypass')                        |
+-------------------------------------------+
| *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 |
+-------------------------------------------+

2026/09/25

ALTER DATABASE .. READ ONLY = 1の使いどころ

MySQL 8.0.22とそれ以降で使える ALTER DATABASE .. READ ONLY = 1 、1mysqldプロセスに1スキーマで運用することが多かったのであんまりメリットを感じてなかった( SET GLOBAL super_read_only = ON でいいじゃん ) けどなんか使いどころに出会ったのでメモ。


大前提として、 1mysqldプロセスに1スキーマ でないこと(当たり前)がある。


これ個人的には「ベストプラクティスは1mysqldプロセスに1スキーマ」だと思っていて、その方が権限の管理もやりやすい(MySQLの権限には「グローバルスコープ」「データベース(スキーマ)スコープ」「テーブルスコープ」(ほぼ例外としての「カラムスコープ」)があるけど、 denylistっぽいこと はグローバルスコープを持っている時にしか効かないし、それ以外は allowlistでしか記述できないのでテーブルスコープで列挙するのは(ROLEで楽になったとはいえ)面倒だし自動化しても下手すると事故りそう。グローバルスコープで権限付けるのは論外)し何かあった時の影響範囲も推測しやすいし調べやすい。

ただ、水平シャードを噛ませた時に1プロセス1スキーマでもスキーマ名を分けておくと将来Multi Source Replicationで集約する時にやりやすい(少なくともJOINできるようになるのでPK / UKの重複を観測して手が考えられる。同じスキーマ名だとレプリケーション組んで壊れるまで重複はわからないし、重複していたら試行錯誤のたびにレプリケーションを組み直す必要があって手間) これはチャンネル単位の CHANGE REPLICATION SOURCE TO REPLICATE_REWRITE_DB で解消しているような気がする。


個人の感想は良いとして、こんな論理レプリケーションを使ったアップグレードパスを考えた時に

これまではmysqld単位で SET GLOBAL read_only = ON でバツっと切り替えるしかなかったものが

スキーマ単位でカナリアリリースできるようになる。

レプリケーションの前方互換性があってgtid_mode = ON なら、切り戻す時の手順も全部バツっと切り替える時とそんなに変わらないはず( SET GLOBAL read_only = ? + レプリケーションの逆流だったものが ALTER DATABASE ? READ ONLY = ? に変わるだけで )

なおフツーの read_only とほぼ同じだけどSuper権限でも書き込めず、レプリケーションソースの binlog_format=ROW ならトリガーぶんもちゃんと READ ONLY SCHEMAの中で更新された(FKのCASCADEは試してない)

俺の使用用途には十分な気がする。


【2026/09/25 18:31】

そういえば  information_schema.schemata でREAD ONLYが見えないの不便だなって思ってたけど、

The READ ONLY option, if enabled, is displayed in the INFORMATION_SCHEMA SCHEMATA_EXTENSIONS table. See Section 28.3.32, “The INFORMATION_SCHEMA SCHEMATA_EXTENSIONS Table”.

https://dev.mysql.com/doc/refman/8.4/en/alter-database.html#alter-database-read-only

危うくFeature Requestを出してしまうところだった… 



2026/09/04

InnoDBのCHECK TABLEが何をやっているのかざっと見てみたメモ

MySQL 8.0.46 ベースに見ていく。

まずは俺の好きな sql/sql_yacc.yy からダイブ。 CHECK TABLE の定義はこのへん。

 9627 check_table_stmt:
 9628           CHECK_SYM table_or_tables table_list opt_mi_check_types
 9629           {
 9630             $$= NEW_PTN PT_check_table_stmt(YYMEM_ROOT, $3,
 9631                                             $4.flags, $4.sql_flags);
 9632           }
 9633         ;
 9634
 9635 opt_mi_check_types:
 9636           %empty { $$.flags = T_MEDIUM; $$.sql_flags= 0; }
 9637         | mi_check_types
 9638         ;
 9639
 9640 mi_check_types:
 9641           mi_check_type
 9642         | mi_check_type mi_check_types
 9643           {
 9644             $$.flags= $1.flags | $2.flags;
 9645             $$.sql_flags= $1.sql_flags | $2.sql_flags;
 9646           }
 9647         ;
 9648
 9649 mi_check_type:
 9650           QUICK
 9651           { $$.flags= T_QUICK;              $$.sql_flags= 0; }
 9652         | FAST_SYM
 9653           { $$.flags= T_FAST;               $$.sql_flags= 0; }
 9654         | MEDIUM_SYM
 9655           { $$.flags= T_MEDIUM;             $$.sql_flags= 0; }
 9656         | EXTENDED_SYM
 9657           { $$.flags= T_EXTEND;             $$.sql_flags= 0; }
 9658         | CHANGED
 9659           { $$.flags= T_CHECK_ONLY_CHANGED; $$.sql_flags= 0; }
 9660         | FOR_SYM UPGRADE_SYM
 9661           { $$.flags= 0;                    $$.sql_flags= TT_FOR_UPGRADE; }
 9662         ;

フラグの定数名が分かるくらいなのでさっさと PT_check_table_stmt に飛ぶ。

3211 Sql_cmd *PT_check_table_stmt::make_cmd(THD *thd) {
3212   thd->lex->sql_command = SQLCOM_CHECK;
3213
3214   LEX *const lex = thd->lex;
3215   Query_block *const select = lex->current_query_block();
3216
3217   if (lex->sphead) {
3218     my_error(ER_SP_BADSTATEMENT, MYF(0), "CHECK");
3219     return nullptr;
3220   }
3221
3222   lex->check_opt.flags |= m_flags;
3223   lex->check_opt.sql_flags |= m_sql_flags;
3224   if (select->add_tables(thd, m_table_list, TL_OPTION_UPDATING, TL_UNLOCK,
3225                          MDL_SHARED_READ))
3226     return nullptr;
3227
3228   thd->lex->alter_info = &m_alter_info;
3229   return new (thd->mem_root) Sql_cmd_check_table(&m_alter_info);
3230 }

Sql_cmd_check_table に飛ぶ。

1755 bool Sql_cmd_check_table::execute(THD *thd) {
1756   Table_ref *first_table = thd->lex->query_block->get_table_list();
1757   thr_lock_type lock_type = TL_READ_NO_INSERT;
1758   bool res = true;
1759   DBUG_TRACE;
1760
1761   if (check_table_access(thd, SELECT_ACL, first_table, true, UINT_MAX, false))
1762     goto error; /* purecov: inspected */
1763   thd->enable_slow_log = opt_log_slow_admin_statements;
1764
1765   res = mysql_admin_table(thd, first_table, &thd->lex->check_opt, "check",
1766                           lock_type, false, false, HA_OPEN_FOR_REPAIR, nullptr,
1767                           &handler::ha_check, 1, m_alter_info, true);
1768
1769   thd->lex->query_block->m_table_list.first = first_table;
1770   thd->lex->query_tables = first_table;
1771
1772 error:
1773   return res;
1774 }

mysql_admin_table に飛ぶ。この時、10番目の引数が handler::ha_check になっているので

 693 /*
 694   RETURN VALUES
 695     false Message sent to net (admin operation went ok)
 696     true  Message should be sent by caller
 697           (admin operation or network communication failed)
 698 */
 699 static bool mysql_admin_table(
 700     THD *thd, Table_ref *tables, HA_CHECK_OPT *check_opt,
 701     const char *operator_name, thr_lock_type lock_type, bool open_for_modify,
 702     bool repair_table_use_frm, uint extra_open_options,
 703     int (*prepare_func)(THD *, Table_ref *, HA_CHECK_OPT *),
 704     int (handler::*operator_func)(THD *, HA_CHECK_OPT *), int check_view,
 705     Alter_info *alter_info, bool need_to_acquire_shared_backup_lock) {

案の定 mysql_admin_table 側では int (handler::*operator_func)(THD *, HA_CHECK_OPT *) として受け取られていて

1107     if (check_opt && (check_opt->sql_flags & TT_FOR_UPGRADE) != 0) {
1108       if (table->table->s->tmp_table) {
1109         result_code = HA_ADMIN_OK;
1110       } else {
1111         dd::String_type snam = dd::make_string_type(table->table->s->db);
1112         dd::String_type tnam =
1113             dd::make_string_type(table->table->s->table_name);
1114
1115         Check_result cr = check_for_upgrade(thd, snam, tnam, [&]() {
1116           DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name));
1117           return (table->table->file->*operator_func)(thd, check_opt);
1118         });
1119
1120         result_code = cr.second;
1121         if (cr.first) {
1122           goto err;
1123         }
1124       }
1125     }
1126     // Some other admin COMMAND
1127     else {
1128       DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name));
1129       result_code = (table->table->file->*operator_func)(thd, check_opt);
1130     }
1131     DBUG_PRINT("admin", ("operator_func returned: %d", result_code));

まあ結局 handler::ha_check を叩くことになる。

4680 int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) {
4681   int error;
4682   assert(table_share->tmp_table != NO_TMP_TABLE || m_lock_type != F_UNLCK);
4683
4684   if ((table->s->mysql_version >= MYSQL_VERSION_ID) &&
4685       (check_opt->sql_flags & TT_FOR_UPGRADE))
4686     return 0;
4687
4688   if (table->s->mysql_version < MYSQL_VERSION_ID) {
4689     // Check for old temporal format if avoid_temporal_upgrade is disabled.
4690     mysql_mutex_lock(&LOCK_global_system_variables);
4691     const bool check_temporal_upgrade = !avoid_temporal_upgrade;
4692     mysql_mutex_unlock(&LOCK_global_system_variables);
4693
4694     if ((error = check_table_for_old_types(table, check_temporal_upgrade)))
4695       return error;
4696     error = ha_check_for_upgrade(check_opt);
4697     if (error && (error != HA_ADMIN_NEEDS_CHECK)) return error;
4698     if (!error && (check_opt->sql_flags & TT_FOR_UPGRADE)) return 0;
4699   }
4700   return check(thd, check_opt);
4701 }

これは結局ストレージエンジン層の check メソッドを呼ぶので

$ grep 'int check(' storage/ -r
storage/archive/ha_archive.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/csv/ha_tina.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/innobase/handler/ha_innodb.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/innobase/handler/ha_innopart.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/myisam/ha_myisam.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/myisammrg/ha_myisammrg.h:  int check(THD *thd, HA_CHECK_OPT *check_opt) override;
storage/temptable/include/temptable/handler.h:  int check(THD *, HA_CHECK_OPT *) override;

こう分かれていて、まあ興味があるのはInnoDBなので storage/innobase/handler/ha_innodb.h から辿っていく。

18152 /** Tries to check that an InnoDB table is not corrupted. If corruption is
18153  noticed, prints to stderr information about it. In case of corruption
18154  may also assert a failure and crash the server.
18155  @return HA_ADMIN_CORRUPT or HA_ADMIN_OK */
18156
18157 int ha_innobase::check(THD *thd,                /*!< in: user thread handle */
18158                        HA_CHECK_OPT *check_opt) /*!< in: check options */
18159 {

一応ここまで check_opt は引き回され続けてはいる。

18218   for (index = m_prebuilt->table->first_index(); index != nullptr;
18219        index = index->next()) {
18220     /* If this is an index being created or dropped, skip */
18221     if (!index->is_committed()) {
18222       continue;
18223     }

このループから、フラグと言うか修飾子というかによらずセカンダリインデックスも範囲なんだってことはわかった。

18225     if (!(check_opt->flags & T_QUICK) && !index->is_corrupted()) {
18226       /* Enlarge the fatal lock wait timeout during
18227       CHECK TABLE. */
18228       srv_fatal_semaphore_wait_extend.fetch_add(1);
18229
18230       bool valid = btr_validate_index(index, m_prebuilt->trx, false);
18231
18232       /* Restore the fatal lock wait timeout after
18233       CHECK TABLE. */
18234       srv_fatal_semaphore_wait_extend.fetch_sub(1);
18235
18236       if (!valid) {
18237         is_ok = false;
18238
18239         push_warning_printf(thd, Sql_condition::SL_WARNING, ER_NOT_KEYFILE,
18240                             "InnoDB: The B-tree of"
18241                             " index %s is corrupted.",
18242                             index->name());
18243         continue;
18244       }
18245     }

CHECK TABLE .. QUICK でない場合は btr_validate_index に入る。

18281     size_t max_threads = thd_parallel_read_threads(m_prebuilt->trx->mysql_thd);
18282
18283     /* Scan this index. */
18284     if (dict_index_is_spatial(index)) {
18285       ret = row_count_rtree_recs(m_prebuilt, &n_rows, &n_dups);
18286       if ((check_opt->flags & T_EXTEND) && (ret == DB_SUCCESS) &&
18287           !(n_rows < n_rows_in_table || n_dups < n_rows - n_rows_in_table)) {
18288         /* For CHECK TABLE EXTENDED; we also want to make sure that MBR stored
18289         in SPATIAL Index is matching the MBR of geometry stored in Clustered
18290         record. */
18291         m_prebuilt->need_to_access_clustered = true;
18292         n_rows = 0;
18293         n_dups = 0;
18294         ret = row_count_rtree_recs(m_prebuilt, &n_rows, &n_dups);
18295       }
18296     } else {
18297       ret = row_scan_index_for_mysql(m_prebuilt, index, max_threads, true,
18298                                      &n_rows);
18299     }

パラレルでやるっぽいことと、空間インデックスは CHECK TABLE .. EXTEND の時だけ舐めることが分かった。

18309     if (ret == DB_INTERRUPTED || thd_killed(m_user_thd)) {
18310       /* Do not report error since this could happen
18311       during shutdown */
18312       break;
18313     }
18314     if (ret != DB_SUCCESS) {
18315       /* Assume some kind of corruption. */
18316       push_warning_printf(thd, Sql_condition::SL_WARNING, ER_NOT_KEYFILE,
18317                           "InnoDB: The B-tree of"
18318                           " index %s is corrupted.",
18319                           index->name());
18320       is_ok = false;
18321       dict_set_corrupted(index);
18322     }
18323
18324     if (index == m_prebuilt->table->first_index()) {
18325       n_rows_in_table = n_rows;
18326     } else if (!(index->type & DICT_FTS) && (n_rows != n_rows_in_table) &&
18327                (!index->is_multi_value()) &&
18328                (!dict_index_is_spatial(index) || (n_rows < n_rows_in_table) ||
18329                 (n_dups < n_rows - n_rows_in_table))) {
18330       push_warning_printf(thd, Sql_condition::SL_WARNING, ER_NOT_KEYFILE,
18331                           "InnoDB: Index '%-.200s' contains %lu"
18332                           " entries, should be %lu.",
18333                           index->name(), (ulong)n_rows, (ulong)n_rows_in_table);
18334       is_ok = false;
18335       dict_set_corrupted(index);
18336     }
18337   }

row_scan_index_for_mysql の結果でメッセージなど出し分けつつ、18337行目がインデックスのforループの終端。あとは結果を返すだけ。

ということで結論。

  • データ行(クラスタインデックス) だけじゃなくてセカンダリキーもちゃんと見る
  • 空間インデックスがある時は CHECK TABLE .. EXTEND で空間インデックスも見る
  • CHECK TABLE .. QUICK の時にかっ飛ばす処理は btr_validate_index だけだけどこれが結構速くなる上に全部のインデックスを読むには読むので、地味にバッファプールをあっためられる( SELECT /*+ INDEX(t PRIMARY) */ COUNT(*) FROM t をインデックスごとにループさせるのとそんな時間変わらない )
​