@@ -72,6 +72,7 @@ def create_restore_job(self):
72
72
self .import_tablespaces_in_target_db ()
73
73
self .hold_write_lock_on_myisam_tables ()
74
74
self .perform_myisam_file_operations ()
75
+ self .perform_post_restoration_validation_and_fixes ()
75
76
self .unlock_all_tables ()
76
77
77
78
@step ("Validate Backup Files" )
@@ -277,6 +278,46 @@ def hold_write_lock_on_myisam_tables(self):
277
278
def perform_myisam_file_operations (self ):
278
279
self ._perform_file_operations (engine = "myisam" )
279
280
281
+ @step ("Validate And Fix Tables" )
282
+ def perform_post_restoration_validation_and_fixes (self ):
283
+ innodb_tables_with_fts = self .get_innodb_tables_with_fts_index ()
284
+ """
285
+ FLUSH TABLES ... FOR EXPORT does not support FULLTEXT indexes.
286
+ https://dev.mysql.com/doc/refman/8.4/en/innodb-table-import.html#:~:text=in%20the%20operation.-,Limitations,-The%20Transportable%20Tablespaces
287
+
288
+ We can either drop + add index.
289
+ Or, run `OPTIMIZE TABLE` on the table to rebuild the index.
290
+ https://mariadb.com/kb/en/optimize-table/#updating-an-innodb-fulltext-index
291
+ """
292
+
293
+ for table in innodb_tables_with_fts :
294
+ if self .is_table_corrupted (table ) and not self .repair_table (table , "innodb" ):
295
+ raise Exception (f"Failed to repair table { table } " )
296
+
297
+ """
298
+ MyISAM table corruption can generally happen due to mismatch of no of records in MYD file.
299
+
300
+ myisamchk can't find and fix this issue.
301
+ Because this out of sync happen after creating a blank MyISAM table and just copying MYF & MYI files.
302
+
303
+ Usually, DB Restart will fix this issue. But we can't do in live database.
304
+ So running `REPAIR TABLE ... USE_FRM` can fix the issue.
305
+ https://dev.mysql.com/doc/refman/8.4/en/myisam-repair.html
306
+ """
307
+ for table in self .myisam_tables :
308
+ if self .is_table_corrupted (table ) and not self .repair_table (table , "myisam" ):
309
+ raise Exception (f"Failed to repair table { table } " )
310
+
311
+ for table in self .innodb_tables :
312
+ if table in innodb_tables_with_fts :
313
+ continue
314
+ """
315
+ If other innodb tables are corrupted,
316
+ We can't repair the table in running database
317
+ """
318
+ if self .is_table_corrupted (table ):
319
+ raise Exception (f"Failed to repair table { table } " )
320
+
280
321
@step ("Unlock All Tables" )
281
322
def unlock_all_tables (self ):
282
323
self ._get_target_db ().execute_sql ("UNLOCK TABLES;" )
@@ -388,8 +429,89 @@ def get_drop_table_statement(self, table_name) -> str:
388
429
389
430
return f"DROP TABLE IF EXISTS `{ table_name } `;"
390
431
432
+ def is_table_corrupted (self , table_name : str ) -> bool :
433
+ result = run_sql_query (self ._get_target_db (), f"CHECK TABLE `{ table_name } ` QUICK;" )
434
+ """
435
+ +-----------------------------------+-------+----------+------------------------------------------------------+
436
+ | Table | Op | Msg_type | Msg_text |
437
+ +-----------------------------------+-------+----------+------------------------------------------------------+
438
+ | _8edd549f4b072174.__global_search | check | warning | Size of indexfile is: 22218752 Should be: 4096 |
439
+ | _8edd549f4b072174.__global_search | check | warning | Size of datafile is: 31303496 Should be: 0 |
440
+ | _8edd549f4b072174.__global_search | check | error | Record-count is not ok; is 152774 Should be: 0 |
441
+ | _8edd549f4b072174.__global_search | check | warning | Found 172605 key parts. Should be: 0 |
442
+ | _8edd549f4b072174.__global_search | check | error | Corrupt |
443
+ +-----------------------------------+-------+----------+------------------------------------------------------+
444
+
445
+ +-------------------------------------------+-------+----------+--------------------------------------------------------+
446
+ | Table | Op | Msg_type | Msg_text |
447
+ +-------------------------------------------+-------+----------+--------------------------------------------------------+
448
+ | _8edd549f4b072174.energy_point_log_id_seq | check | note | The storage engine for the table doesn't support check |
449
+ +-------------------------------------------+-------+----------+--------------------------------------------------------+
450
+ """ # noqa: E501
451
+ isError = False
452
+ for row in result :
453
+ if row [2 ] == "error" :
454
+ isError = True
455
+ break
456
+ return isError
457
+
458
+ def repair_table (self , table_name : str , engine : str ) -> bool :
459
+ if engine == "innodb" :
460
+ result = run_sql_query (self ._get_target_db (), f"OPTIMIZE TABLE `{ table_name } `;" )
461
+ elif engine == "myisam" :
462
+ result = run_sql_query (self ._get_target_db (), f"REPAIR TABLE `{ table_name } ` USE_FRM;" )
463
+ else :
464
+ raise Exception (f"Engine { engine } is not supported" )
465
+ """
466
+ +---------------------------------------------------+--------+----------+----------+
467
+ | Table | Op | Msg_type | Msg_text |
468
+ +---------------------------------------------------+--------+----------+----------+
469
+ | _8edd549f4b072174.tabInsights Query Execution Log | repair | status | OK |
470
+ +---------------------------------------------------+--------+----------+----------+
471
+
472
+ Msg Type can be status, error, info, note, or warning
473
+ """
474
+ isErrorOccurred = False
475
+ for row in result :
476
+ if row [2 ] == "error" :
477
+ isErrorOccurred = True
478
+ break
479
+
480
+ return not isErrorOccurred
481
+
482
+ def get_innodb_tables_with_fts_index (self ):
483
+ rows = run_sql_query (
484
+ self ._get_target_db (),
485
+ f"""
486
+ SELECT
487
+ DISTINCT(t.TABLE_NAME)
488
+ FROM
489
+ information_schema.STATISTICS s
490
+ JOIN
491
+ information_schema.TABLES t
492
+ ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
493
+ AND s.TABLE_NAME = t.TABLE_NAME
494
+ WHERE
495
+ s.INDEX_TYPE = 'FULLTEXT'
496
+ AND t.TABLE_SCHEMA = '{ self .target_db } '
497
+ AND t.ENGINE = 'InnoDB'
498
+ """ ,
499
+ )
500
+ return [row [0 ] for row in rows ]
501
+
391
502
def __del__ (self ):
392
503
if self ._target_db_instance is not None :
393
504
self ._target_db_instance .close ()
394
505
if self ._target_db_instance_for_myisam is not None :
395
506
self ._target_db_instance_for_myisam .close ()
507
+
508
+
509
+ def run_sql_query (db : CustomPeeweeDB , query : str ) -> list [str ]:
510
+ """
511
+ Return the result of the query as a list of rows
512
+ """
513
+ cursor = db .execute_sql (query )
514
+ if not cursor .description :
515
+ return []
516
+ rows = cursor .fetchall ()
517
+ return [row for row in rows ]
0 commit comments