<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>TwinDB minimalist</title>
	<atom:link href="https://twindb.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://twindb.com</link>
	<description>Backup and Data Recovery Solutions for MySQL</description>
	<lastBuildDate>Sat, 06 Mar 2021 17:57:36 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.8.1</generator>

<image>
	<url>https://twindb.com/wp-content/uploads/2020/09/favicon.png</url>
	<title>TwinDB minimalist</title>
	<link>https://twindb.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Recovery After DROP TABLE, With innodb_file_per_table ON</title>
		<link>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-on/</link>
					<comments>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-on/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Sat, 06 Mar 2021 17:57:36 +0000</pubDate>
				<category><![CDATA[MySQL Data Recovery]]></category>
		<category><![CDATA[c_parser]]></category>
		<category><![CDATA[DROP DATABASE]]></category>
		<category><![CDATA[DROP TABLE]]></category>
		<category><![CDATA[innodb_file_per_table=ON]]></category>
		<category><![CDATA[recovery after drop table]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=319</guid>

					<description><![CDATA[Author Andriy Lysyuk. Introduction In the previous post, we described a situation when the UnDrop For InnoDB toolkit can be used to recover an accidentally dropped table with innodb_file_per_table=OFF.In this post, we’ll show how to recover MySQL tables or databases if innodb_file_per_table is ON. This option tells InnoDB to store each table with a user&#8230; <a class="more-link" href="https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-on/">Continue reading <span class="screen-reader-text">Recovery After DROP TABLE, With innodb_file_per_table ON</span></a>]]></description>
										<content:encoded><![CDATA[
<p><em>Author <a href="https://www.linkedin.com/in/lysyuk/">Andriy Lysyuk</a>.</em></p>



<h2>Introduction</h2>



<p>In the <a href="https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/" data-type="post" data-id="314">previous post</a>, we described a situation when the <a href="https://twindb.com/undrop-for-innodb/" data-type="page" data-id="262">UnDrop For InnoDB</a> toolkit can be used to recover an accidentally dropped table with <code>innodb_file_per_table=OFF</code>.<br>In this post, we’ll show how to recover MySQL tables or databases if <code>innodb_file_per_table</code> is <code>ON</code>. This option tells InnoDB to store each table with a user in a separate data file.</p>



<p>For the recovery test, we’ll use the same sakila database that we used in the <a href="https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/" data-type="post" data-id="314">previous post</a>.</p>



<pre class="wp-block-code"><code>root@test:/var/lib/mysql/sakila# ls -la
total 23468
drwx------ 2 mysql mysql     4096 Jul 15 04:26 ./
drwx------ 6 mysql mysql     4096 Jul 15 04:26 ../
-rw-rw---- 1 mysql mysql     8694 Jul 15 04:26 actor.frm
-rw-rw---- 1 mysql mysql   114688 Jul 15 04:26 actor.ibd
-rw-rw---- 1 mysql mysql     2871 Jul 15 04:26 actor_info.frm
-rw-rw---- 1 mysql mysql     8840 Jul 15 04:26 address.frm
-rw-rw---- 1 mysql mysql   163840 Jul 15 04:26 address.ibd
-rw-rw---- 1 mysql mysql     8648 Jul 15 04:26 category.frm
-rw-rw---- 1 mysql mysql    98304 Jul 15 04:26 category.ibd
-rw-rw---- 1 mysql mysql     8682 Jul 15 04:26 city.frm
-rw-rw---- 1 mysql mysql   114688 Jul 15 04:26 city.ibd
-rw-rw---- 1 mysql mysql     8652 Jul 15 04:26 country.frm
-rw-rw---- 1 mysql mysql    98304 Jul 15 04:26 country.ibd
...
-rw-rw---- 1 mysql mysql       36 Jul 15 04:26 upd_film.TRN
root@test:/var/lib/mysql/sakila#</code></pre>



<p>Note the two files related to the table country: <code>country.frm</code>, <code>country.ibd</code>.<br>We will drop this table and try to recover it. First, we take the checksum and preview the records this table contains:</p>



<pre class="wp-block-code"><code>mysql> SELECT * FROM country LIMIT 10;
+------------+----------------+---------------------+
| country_id | country        | last_update         |
+------------+----------------+---------------------+
|          1 | Afghanistan    | 2006-02-15 04:44:00 |
|          2 | Algeria        | 2006-02-15 04:44:00 |
|          3 | American Samoa | 2006-02-15 04:44:00 |
|          4 | Angola         | 2006-02-15 04:44:00 |
|          5 | Anguilla       | 2006-02-15 04:44:00 |
|          6 | Argentina      | 2006-02-15 04:44:00 |
|          7 | Armenia        | 2006-02-15 04:44:00 |
|          8 | Australia      | 2006-02-15 04:44:00 |
|          9 | Austria        | 2006-02-15 04:44:00 |
|         10 | Azerbaijan     | 2006-02-15 04:44:00 |
+------------+----------------+---------------------+
10 rows in set (0.00 sec)
 
mysql> CHECKSUM TABLE country;
+----------------+------------+
| Table          | Checksum   |
+----------------+------------+
| sakila.country | 3658016321 |
+----------------+------------+
1 row in set (0.00 sec)
 
mysql> SELECT COUNT(*) FROM country;
+----------+
| COUNT(*) |
+----------+
|      109 |
+----------+
1 row in set (0.00 sec)</code></pre>



<h2>Accidental Drop</h2>



<p>Now, we drop the table and look for files related to the table. As you can see from the list, files with <strong>country</strong> table data are no longer there:</p>



<pre class="wp-block-code"><code>mysql> SET foreign_key_checks=OFF;
Query OK, 0 rows affected (0.00 sec)
 
mysql> DROP TABLE country;
Query OK, 0 rows affected (0.00 sec)
 
root@test:~# cd /var/lib/mysql/sakila/
root@test:/var/lib/mysql/sakila# ls -la
total 23360
drwx------ 2 mysql mysql     4096 Jul 15 04:33 ./
drwx------ 6 mysql mysql     4096 Jul 15 04:26 ../
-rw-rw---- 1 mysql mysql     8694 Jul 15 04:26 actor.frm
-rw-rw---- 1 mysql mysql   114688 Jul 15 04:26 actor.ibd
-rw-rw---- 1 mysql mysql     2871 Jul 15 04:26 actor_info.frm
-rw-rw---- 1 mysql mysql     8840 Jul 15 04:26 address.frm
-rw-rw---- 1 mysql mysql   163840 Jul 15 04:26 address.ibd
-rw-rw---- 1 mysql mysql     8648 Jul 15 04:26 category.frm
-rw-rw---- 1 mysql mysql    98304 Jul 15 04:26 category.ibd
-rw-rw---- 1 mysql mysql     8682 Jul 15 04:26 city.frm
-rw-rw---- 1 mysql mysql   114688 Jul 15 04:26 city.ibd
-rw-rw---- 1 mysql mysql       40 Jul 15 04:26 customer_create_date.TRN
-rw-rw---- 1 mysql mysql     8890 Jul 15 04:26 customer.frm
-rw-rw---- 1 mysql mysql   196608 Jul 15 04:26 customer.ibd
-rw-rw---- 1 mysql mysql     1900 Jul 15 04:26 customer_list.frm
-rw-rw---- 1 mysql mysql      297 Jul 15 04:26 customer.TRG
-rw-rw---- 1 mysql mysql       65 Jul 15 04:26 db.opt
...
-rw-rw---- 1 mysql mysql       36 Jul 15 04:26 upd_film.TRN
root@test:/var/lib/mysql/sakila#</code></pre>



<h2>Recovery After DROP TABLE</h2>



<p>This situation is a little bit more complex, since we need to recover a deleted file. If the database server has active communication with HDD, it’s possible that the deleted file will be rewritten with other data. Therefore, it’s critical to stop the server and to mount the partition read-only. But for the test we will just stop mysql service and continue with the recovery.</p>



<pre class="wp-block-code"><code>root@test:/var/lib/mysql/sakila# service mysql stop
mysql stop/waiting</code></pre>



<p>Despite the fact that user data is stored in separate files per each table, data dictionary is still stored in the ibdata1 file. That’s why we need to use <code>stream_parser</code> for <code>/var/lib/mysql/ibdata1</code>.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Undrop for InnoDB overview" width="750" height="422" src="https://www.youtube.com/embed/-1LeLhGjAWM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div><figcaption>Check out a video with the toolkit overview and usage instructions.</figcaption></figure>



<p>In order to find <code>table_id</code> and <code>index_id</code> for the table country, we will use the dictionary stored in <code>SYS_TABLES</code> and <code>SYS_INDEXES</code>. We will fetch the data from the <code>ibdata1</code> file. The dictionary records are always in <a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format.html#innodb-row-format-redundant">REDUNDANT</a> format, therefore we specify option <code>-4</code>. We assume that the MySQL server has flushed changes to the disk, so we add the <code>-D</code> option which means “find deleted records”. <code>SYS_TABLES</code> information is stored in the file with <code>index_id=1</code> which is file <code>pages-ibdata1/FIL_PAGE_INDEX./0000000000000001.page</code>:</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser -4D \
    -f ./pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page \
    -t ./dictionary/SYS_TABLES.sql \
    | grep country
000000000CDC  62000001960684  SYS_TABLES      "sakila/country"        228     3       1       0       0       ""      88
000000000CDC  62000001960684  SYS_TABLES      "sakila/country"        228     3       1       0       0       ""      88

SET FOREIGN_KEY_CHECKS=0;
LOAD DATA LOCAL INFILE '/home/asterix/undrop-for-innodb/dumps/default/SYS_TABLES' REPLACE INTO TABLE `SYS_TABLES` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_TABLES\t' (`NAME`, `ID`, `N_COLS`, `TYPE`, `MIX_ID`, `MIX_LEN`, `CLUSTER_NAME`, `SPACE`);
 
root@test:~/undrop-for-innodb#</code></pre>



<p>We can see that the country table has <code>table_id=228</code>. The next step would be to find the <code>PRIMARY</code> index of the table country. For this purpose, we take the records of the <code>SYS_INDEXES</code> table from the file <code>0000000000000003.page</code> (<code>SYS_INDEXES</code> table contains mapping between <code>table_id</code> and <code>index_id</code>). The structure of <code>SYS_INDEXES</code> is specified with a <code>-t</code> option.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser -4D
    -f ./pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page \
    -t ./dictionary/SYS_INDEXES.sql \
    | grep 228
000000000CDC    620000019605A8  SYS_INDEXES     228     547     "PRIMARY"       1       3       88      4294967295
000000000CDC    620000019605A8  SYS_INDEXES     228     547     "PRIMARY"       1       3       88      4294967295

SET FOREIGN_KEY_CHECKS=0;
LOAD DATA LOCAL INFILE '/home/asterix/undrop-for-innodb/dumps/default/SYS_INDEXES' REPLACE INTO TABLE `SYS_INDEXES` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_INDEXES\t' (`TABLE_ID`, `ID`, `NAME`, `N_FIELDS`, `TYPE`, `SPACE`, `PAGE_NO`);
 
root@test:~/undrop-for-innodb#</code></pre>



<p>Almost There!</p>



<p>We can see that <code>index_id</code> of the dropped table country is 547. The following step is different from the step we took in the case with <code>innodb_file_per_table=OFF</code>. Since there is no file with data available, we’ll scan through the whole partition as a raw device and look for data that fits the expected structure of the database pages. By the way, you can use this <a href="https://twindb.com/recover-corrupt-mysql-database/" data-type="post" data-id="271">approach with corrupted data files</a>. If some data is corrupted, the recovery tool can perform partial data recovery. In the options of the tool we specify device name and device size (can be approximate).</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb#./stream_parser \
    -f /dev/vda \
    -t 20000000k
Opening file: /dev/vda
File information:
 
ID of device containing file:            5
inode number:                         6411
protection:                          60660 (block device)
number of hard links:                    1
user ID of owner:                        0
group ID of owner:                       6
device ID (if special file):         64768
blocksize for filesystem I/O:         4096
number of blocks allocated:              0
time of last access:            1405411377 Tue Jul 15 04:02:57 2014
time of last modification:      1404625158 Sun Jul  6 01:39:18 2014
time of last status change:     1404625158 Sun Jul  6 01:39:18 2014
total size, in bytes:                    0 (0.000 exp(+0))
 
Size to process:               20480000000 (19.073 GiB)
Worker(0): 1.06% done. 2014-07-15 04:57:37 ETA(in 00:01:36). Processing speed: 199.848 MiB/sec
Worker(0): 2.09% done. 2014-07-15 04:57:37 ETA(in 00:01:35). Processing speed: 199.610 MiB/sec
Worker(0): 3.11% done. 2014-07-15 04:59:13 ETA(in 00:03:09). Processing speed: 99.805 MiB/sec
...
Worker(0): 97.33% done. 2014-07-15 04:57:15 ETA(in 00:00:05). Processing speed: 99.828 MiB/sec
Worker(0): 98.35% done. 2014-07-15 04:57:20 ETA(in 00:00:06). Processing speed: 49.941 MiB/sec
Worker(0): 99.38% done. 2014-07-15 04:57:17 ETA(in 00:00:01). Processing speed: 99.961 MiB/sec
All workers finished in 77 sec
root@test:~/undrop-for-innodb#</code></pre>



<p>Stream parser stores the resulted files with pages to the folder <code>pages-vda</code> (name derived from the file given to <code>stream_parser</code>). We can see that the necessary index is present in the files.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb/pages-vda/FIL_PAGE_INDEX# ls -la | grep 547
-rw-r--r-- 1 root root    32768 Jul 15 04:57 0000000000000547.page
root@test:~/undrop-for-innodb/pages-vda/FIL_PAGE_INDEX#</code></pre>



<p>We will look for the data in the file <code>0000000000000547.page</code>. The <code>c_parser</code> tool fetches and prints records from the pages file in a TAB-Separated-Values format.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser -6 \
    -f pages-vda/FIL_PAGE_INDEX/0000000000000547.page \
    -t sakila/country.sql \
    | head -5
-- Page id: 3, Format: COMPACT, Records list: Valid, Expected records: (109 109)
000000000C4B    F30000038C0110  country 1       "Afghanistan"   "2006-02-15 04:44:00"
000000000C4B    F30000038C011B  country 2       "Algeria"       "2006-02-15 04:44:00"
000000000C4B    F30000038C0126  country 3       "American Samoa"        "2006-02-15 04:44:00"
000000000C4B    F30000038C0131  country 4       "Angola"        "2006-02-15 04:44:00"
root@test:~/undrop-for-innodb#</code></pre>



<p>The result looks valid, so we prepare files for loading data back to the database. The <code>LOAD DATA INFILE</code> command with necessary options is sent to the STDERR.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser -6 \
    -f pages-vda/FIL_PAGE_INDEX/0000000000000547.page \
    -t sakila/country.sql \
    > dumps/default/country \
    2> dumps/default/country_load.sql</code></pre>



<h2>Loading Data Back To The Database</h2>



<p>Now, we’re going to load data back to the database. Before loading the data, we create an empty structure of the table country:</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# service mysql start

root@test:~/undrop-for-innodb# mysql -uroot -p
 
mysql> use sakila;
mysql> source sakila/country.sql
Query OK, 0 rows affected (0.00 sec)
...
 
Query OK, 0 rows affected (0.00 sec)
 
mysql> show create table country\G
*************************** 1. row ***************************
       Table: country
Create Table: CREATE TABLE `country` (
  `country_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `country` varchar(50) NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=110 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)</code></pre>



<p>And now we are loading the data itself.</p>



<pre class="wp-block-code"><code>root@testB:~/undrop-for-innodb# mysql --local-infile -uroot -p
mysql> USE sakila;
mysql> source dumps/default/country_load.sql
Query OK, 0 rows affected (0.00 sec)
 
Query OK, 327 rows affected (0.00 sec)
Records: 218  Deleted: 109  Skipped: 0  Warnings: 0 </code></pre>



<h2>Checking Data Quality</h2>



<p>So, the last thing that remaining is to check the quality of recovered data. We’ll preview several records, calculate the total number of records and checksum.</p>



<pre class="wp-block-code"><code>mysql> SELECT COUNT(*) FROM country;
+----------+
| COUNT(*) |
+----------+
|      109 |
+----------+
1 row in set (0.00 sec)
 
mysql> SELECT * FROM country LIMIT 5;
+------------+----------------+---------------------+
| country_id | country        | last_update         |
+------------+----------------+---------------------+
|          1 | Afghanistan    | 2006-02-15 04:44:00 |
|          2 | Algeria        | 2006-02-15 04:44:00 |
|          3 | American Samoa | 2006-02-15 04:44:00 |
|          4 | Angola         | 2006-02-15 04:44:00 |
|          5 | Anguilla       | 2006-02-15 04:44:00 |
+------------+----------------+---------------------+
5 rows in set (0.00 sec)
 
mysql> CHECKSUM TABLE country;
+----------------+------------+
| Table          | Checksum   |
+----------------+------------+
| sakila.country | 3658016321 |
+----------------+------------+
1 row in set (0.00 sec)</code></pre>



<p>So, we’re in luck. Despite the fact that we used the system volume (which is not the recommended practice) for mysql data, and that we haven’t re-mounted partition as read-only (and other processes were continuing to perform writing to the disk), we managed to recover all the records. Calculated checksum after the recovery (<code>3658016321</code>) is equal to the checksum taken before the drop (<code>3658016321</code>).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-on/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Recovery After DROP TABLE, With innodb_file_per_table OFF</title>
		<link>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/</link>
					<comments>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Sat, 06 Mar 2021 16:51:30 +0000</pubDate>
				<category><![CDATA[MySQL Data Recovery]]></category>
		<category><![CDATA[c_parser]]></category>
		<category><![CDATA[DROP DATABASE]]></category>
		<category><![CDATA[DROP TABLE]]></category>
		<category><![CDATA[innodb_file_per_table=OFF]]></category>
		<category><![CDATA[recovery after drop table]]></category>
		<category><![CDATA[stream_parser]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=314</guid>

					<description><![CDATA[Author Andriy Lysyuk. Introduction Unfortunately, human mistakes are inevitable. That’s how life is. Wrong DROP DATABASE or DROP TABLE may destroy critical data on the MySQL server. Obviously, backups would help, however they’re not always available. This situation is frightening but not hopeless. In many cases it’s possible to recover almost all the data that&#8230; <a class="more-link" href="https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/">Continue reading <span class="screen-reader-text">Recovery After DROP TABLE, With innodb_file_per_table OFF</span></a>]]></description>
										<content:encoded><![CDATA[
<p><em>Author <a href="https://www.linkedin.com/in/lysyuk/">Andriy Lysyuk</a>.</em></p>



<h2>Introduction</h2>



<p>Unfortunately, human mistakes are inevitable. That’s how life is. Wrong <code>DROP DATABASE</code> or <code>DROP TABLE</code> may destroy critical data on the MySQL server. Obviously, backups would help, however they’re not always available. This situation is frightening but not hopeless. In many cases it’s possible to recover almost all the data that was in the database or table.<br>Let’s look at how we can do it. The recovery plan depends on whether InnoDB kept all data in a single ibdata1 or each table had its own tablespace. In this post we will consider the case when <code>innodb_file_per_table=OFF</code>. This option assumes that all tables are stored in a common file, usually located at <code>/var/lib/mysql/ibdata1</code>.</p>



<h2>One Wrong Move, And The Table’s Gone</h2>



<p>For our scenario, we use the sakila test database that comes together with the tool.<br>Supposedly, we drop my mistake table actor:</p>



<pre class="wp-block-code"><code>mysql> SELECT * FROM actor LIMIT 10;
+----------+------------+--------------+---------------------+
| actor_id | first_name | last_name    | last_update         |
+----------+------------+--------------+---------------------+
|        1 | PENELOPE   | GUINESS      | 2006-02-15 04:34:33 |
|        2 | NICK       | WAHLBERG     | 2006-02-15 04:34:33 |
|        3 | ED         | CHASE        | 2006-02-15 04:34:33 |
|        4 | JENNIFER   | DAVIS        | 2006-02-15 04:34:33 |
|        5 | JOHNNY     | LOLLOBRIGIDA | 2006-02-15 04:34:33 |
|        6 | BETTE      | NICHOLSON    | 2006-02-15 04:34:33 |
|        7 | GRACE      | MOSTEL       | 2006-02-15 04:34:33 |
|        8 | MATTHEW    | JOHANSSON    | 2006-02-15 04:34:33 |
|        9 | JOE        | SWANK        | 2006-02-15 04:34:33 |
|       10 | CHRISTIAN  | GABLE        | 2006-02-15 04:34:33 |
+----------+------------+--------------+---------------------+
10 rows in set (0.00 sec)
mysql> CHECKSUM TABLE actor;
+--------------+------------+
| Table        | Checksum   |
+--------------+------------+
| sakila.actor | 3596356558 |
+--------------+------------+
1 row in set (0.00 sec)
 
mysql> SET foreign_key_checks=OFF
mysql> DROP TABLE actor;
Query OK, 0 rows affected (0.00 sec)</code></pre>



<h2>Recovery After DROP TABLE From ibdata1</h2>



<p>The table is gone, but the information contained in the table can still be in the database file. The data remains untouched until InnoDB reuses free pages. Hurry up and stop MySQL ASAP!<br>For the recovery, we use the <a rel="noreferrer noopener" href="https://github.com/twindb/undrop-for-innodb" target="_blank">Undrop For InnoDB</a> toolkit.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Undrop for InnoDB overview" width="750" height="422" src="https://www.youtube.com/embed/-1LeLhGjAWM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div><figcaption>Check out a video with the toolkit overview and usage instructions.</figcaption></figure>



<p>InnoDB stores all data in B+tree indexes. A table has one clustered index PRIMARY, all fields are stored there. Thus, if the table has secondary keys, each key has an index. Each index is identified by index_id.</p>



<p>Consequently, if we want to recover a table, we have to find all pages that belong to a particular index_id.</p>



<p><code>stream_parser</code> reads the InnoDB tablespace and sorts InnoDB pages by type and by index_id.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./stream_parser -f /var/lib/mysql/ibdata1
Opening file: /var/lib/mysql/ibdata1
File information:
 
ID of device containing file:        64768
inode number:                      1190268
protection:                         100660 (regular file)
number of hard links:                    1
user ID of owner:                      106
group ID of owner:                     114
device ID (if special file):             0
blocksize for filesystem I/O:         4096
number of blocks allocated:          69632
time of last access:            1404842312 Tue Jul  8 13:58:32 2014
time of last modification:      1404842478 Tue Jul  8 14:01:18 2014
time of last status change:     1404842478 Tue Jul  8 14:01:18 2014
total size, in bytes:             35651584 (34.000 MiB)
 
Size to process:                  35651584 (34.000 MiB)
All workers finished in 0 sec
root@test: ~/undrop-for-innodb#</code></pre>



<p>Data from the database pages is saved by the <code>stream_parser</code> to folder <code>pages-ibdata1</code>:</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb/pages-ibdata1/FIL_PAGE_INDEX# ls
0000000000000001.page  0000000000000121.page  0000000000000382.page
0000000000000395.page  0000000000000408.page  0000000000000421.page
0000000000000434.page  0000000000000447.page  0000000000000002.page
...
0000000000000406.page  0000000000000419.page  0000000000000432.page
0000000000000445.page  0000000000000120.page  0000000000000381.page
0000000000000394.page  0000000000000407.page  0000000000000420.page
0000000000000433.page  0000000000000446.page
root@test: ~/undrop-for-innodb/pages-ibdata1/FIL_PAGE_INDEX</code></pre>



<p>Now, each index_id from the InnoDB tablespace is saved in a separate file. We can use <code>c_parser</code> to fetch records from the pages. But we need to know what index_id corresponds to table <code>sakila/actor</code>. This information we can acquire from the dictionary – <code>SYS_TABLES</code> and <code>SYS_INDEXES</code>.</p>



<h2>Dictionaries</h2>



<p><code>SYS_TABLES</code> is always stored in file <code>index_id</code> 1 which is file <code>pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page</code><br>Let’s find the table_id of <code>sakila/actor</code>. If MySQL had enough time to flush changes to disk, then add the <code>-D</code> option which means “find deleted records”. The dictionary is always in <a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format.html#innodb-row-format-redundant">REDUNDANT</a> format, so we specify option <code>-4</code>:</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser \
    -4D
    -f pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page \
    -t dictionary/SYS_TABLES.sql \
    | grep sakila/actor
000000000B28  2A000001430D4D  SYS_TABLES  "sakila/actor"  158  4  1 0   0   ""  0
000000000B28  2A000001430D4D  SYS_TABLES  "sakila/actor"  158  4  1 0   0   ""  0
 </code></pre>



<p>Note number <strong>158</strong> right after the table name. This is <code>table_id</code>.</p>



<p>The next thing do is to find the index id of the <code>PRIMARY</code> index of table actor. For this purpose, we fetch records of <code>SYS_INDEXES</code> from the file <code>0000000000000003.page</code> (this table will contain information about <code>index_id</code> and <code>table_id</code>). The structure of <code>SYS_INDEXES</code> is passed with <code>-t</code> option.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb$ ./c_parser \
    -4D \
    -f pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page \
    -t dictionary/SYS_INDEXES.sql\
    | grep -w 158
000000000B28    2A000001430BCA  SYS_INDEXES     158     376     "PRIMARY"       1       3       0       4294967295
000000000B28    2A000001430C3C  SYS_INDEXES     158     377     "idx\_actor\_last\_name"        1       0       0       4294967295
000000000B28    2A000001430BCA  SYS_INDEXES     158     376     "PRIMARY"       1       3       0       4294967295
000000000B28    2A000001430C3C  SYS_INDEXES     158     377     "idx\_actor\_last\_name"        1       0       0       4294967295
 </code></pre>



<p>As you can see from the output, the necessary <code>index_id</code> is <strong>376</strong>. Therefore, we look for the actor data in the file <code>0000000000000376.page</code>.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# ./c_parser \
    -6 \
    -f pages-ibdata1/FIL_PAGE_INDEX/0000000000000376.page \
    -t sakila/actor.sql \
    |  head -5
-- Page id: 895, Format: COMPACT, Records list: Valid, Expected records: (200 200)
000000000AA0    B60000035D0110  actor   1       "PENELOPE"      "GUINESS"       "2006-02-15 04:34:33"
000000000AA0    B60000035D011B  actor   2       "NICK"  "WAHLBERG"      "2006-02-15 04:34:33"
000000000AA0    B60000035D0126  actor   3       "ED"    "CHASE" "2006-02-15 04:34:33"
000000000AA0    B60000035D0131  actor   4       "JENNIFER"      "DAVIS" "2006-02-15 04:34:33"
root@test:~/undrop-for-innodb#</code></pre>



<p>The resulting output looks correct, so let’s save the dump in a file. To make loading simpler, <code>c_parser</code> outputs <code>LOAD DATA INFILE</code> command to <code>STDERR</code>.</p>



<p>We use the default location of these files: <code>dumps/default</code>.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# mkdir -p dumps/default
root@test:~/undrop-for-innodb# ./c_parser \
    -6 \
    -f pages-ibdata1/FIL_PAGE_INDEX/0000000000000376.page\
    -t sakila/actor.sql \
    > dumps/default/actor \
    2> dumps/default/actor_load.sql</code></pre>



<p>And here’s a command to load the table.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# cat dumps/default/actor_load.sql
SET FOREIGN_KEY_CHECKS=0;
LOAD DATA LOCAL 
INFILE '/home/asterix/undrop-for-innodb/dumps/default/actor' 
REPLACE INTO TABLE `actor` 
FIELDS TERMINATED BY '\t' 
OPTIONALLY ENCLOSED BY '"' 
LINES STARTING BY 'actor\t' 
(`actor_id`, `first_name`, `last_name`, `last_update`);
root@test:~/undrop-for-innodb#</code></pre>



<h2>Load Data Back To The Database</h2>



<p>Now, it’s time to recover the data into the database. But before loading the dump we need to create an empty structure of the table actor:</p>



<pre class="wp-block-code"><code>mysql> source sakila/actor.sql
mysql> show create table actor\G
*************************** 1. row ***************************
       Table: actor
Create Table: CREATE TABLE `actor` (
  `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`actor_id`),
  KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql></code></pre>



<p>Now, the table actor is ready. We can load our data after recovery.</p>



<pre class="wp-block-code"><code>root@test:~/undrop-for-innodb# mysql --local-infile -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
...
mysql> USE sakila;
mysql> source dumps/default/actor_load.sql
Query OK, 0 rows affected (0.00 sec)
 
Query OK, 600 rows affected (0.01 sec)
Records: 400  Deleted: 200  Skipped: 0  Warnings: 0</code></pre>



<h2>Checking Recovered Data</h2>



<p>And finally, checking data quality. We will see the total number of records, preview several records and calculate checksum.</p>



<pre class="wp-block-code"><code>mysql> SELECT COUNT(*) FROM actor;
+----------+
| COUNT(*) |
+----------+
|      200 |
+----------+
1 row in set (0.00 sec)
 
mysql> SELECT * FROM actor LIMIT 5;
+----------+------------+--------------+---------------------+
| actor_id | first_name | last_name    | last_update         |
+----------+------------+--------------+---------------------+
|        1 | PENELOPE   | GUINESS      | 2006-02-15 04:34:33 |
|        2 | NICK       | WAHLBERG     | 2006-02-15 04:34:33 |
|        3 | ED         | CHASE        | 2006-02-15 04:34:33 |
|        4 | JENNIFER   | DAVIS        | 2006-02-15 04:34:33 |
|        5 | JOHNNY     | LOLLOBRIGIDA | 2006-02-15 04:34:33 |
+----------+------------+--------------+---------------------+
5 rows in set (0.00 sec)
 
mysql> CHECKSUM TABLE actor;
+--------------+------------+
| Table        | Checksum   |
+--------------+------------+
| sakila.actor | 3596356558 |
+--------------+------------+
1 row in set (0.00 sec)</code></pre>



<p>As you can see, checksum after recovery is <code>3596356558</code> which is equal to the checksum taken before the accidental drop of the table. Therefore, we can be sure that all the data was recovered correctly.<br>In the next posts we will see other cases of recovery.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/recovery-after-drop-table-with-innodb_file_per_table-off/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Verify MySQL Backups With TwinDB Backup Tool</title>
		<link>https://twindb.com/verify-mysql-backups-with-twindb-backup-tool/</link>
					<comments>https://twindb.com/verify-mysql-backups-with-twindb-backup-tool/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Fri, 25 Sep 2020 17:07:04 +0000</pubDate>
				<category><![CDATA[MySQL Backup]]></category>
		<category><![CDATA[backup]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=286</guid>

					<description><![CDATA[By Maksym Kryva. If you don’t verify backups you may safely assume you don’t have them. It often happens that MySQL backups can be invalid or broken due to a software bug, or some hidden corruption. If you are lucky enough, hours and days will be needed to resurrect a database from a bad backup&#8230; <a class="more-link" href="https://twindb.com/verify-mysql-backups-with-twindb-backup-tool/">Continue reading <span class="screen-reader-text">Verify MySQL Backups With TwinDB Backup Tool</span></a>]]></description>
										<content:encoded><![CDATA[
<p>By <a href="https://www.linkedin.com/in/okadzaki/">Maksym Kryva</a>.</p>



<p>If you don’t verify backups you may safely assume you don’t have them.</p>



<p>It often happens that MySQL backups can be invalid or broken due to a software bug, or some hidden corruption. If you are lucky enough, hours and days will be needed to resurrect a database from a bad backup copy. If you ran out of luck quota, you may lose a lot of data. Hence the importance of data backup verification. Not many companies do backups, and even less verify them. To make the verification problem easier, we have added a <code>verify</code> command to the <a href="https://github.com/twindb/backup">TwinDB Backup Tool</a>.</p>



<p>What the command does is that it takes a backup copy, restores it, prepares (applies redo logs, fixes permissions and so on) and runs a MySQL instance on it. Then it checks if the recovered database is healthy. You can verify the backups either on the same server where the backup was taken, or on a dedicated “verification” machine.</p>



<h2>Usage</h2>



<pre class="wp-block-preformatted"># twindb-backup verify mysql --help
Usage: twindb-backup verify mysql [OPTIONS] [BACKUP_COPY]
 
  Verify backup
 
Options:
  --dst TEXT       Directory where to restore the backup copy  [default:
                   /tmp/]
  --hostname TEXT  If backup_copy is latest this option specifies hostname
                   where the backup copy was taken.
  --help           Show this message and exit.</pre>



<ul><li><code>backup_copy</code> is a backup copy name. You can get it from the <code>twindb-backup ls</code> output.  Or you can pass <code>latest</code> for verifying the most recent MySQL backup.</li><li><code>hostname</code> – if you verify the backup on another machine, you have to specify what host the backup was taken from. If you run it without specifying the hostname, it will use the hostname of the local machine.</li><li><code>dst</code> is a directory for restored mysql backup. By default it’s <code>/tmp</code>.</li></ul>



<p>For example:</p>



<pre class="wp-block-preformatted">twindb-backup verify mysql \
    /path/to/twindb-server-backups/master1/hourly/files/_home-2017-11-13_16_43_17.tar.gz \
    --dst /var/lib/mysql</pre>



<p>To verify a backup, <code>twindb-backup</code> gets it from destinations such as <code>S3</code>, <code>SSH</code>, or <code>Local</code>. After this, <code>twindb-backup</code> runs <a href="https://www.percona.com/doc/percona-xtrabackup/">innobackupex</a> to restore the backup from the archive.</p>



<p>This feature works transparently with both full and incremental backups.</p>



<p>Besides, if you configure the twindb-backup tool on export data to DataDog (watch out for our next post :), you can monitor restore time and alert your team about invalid backups, or if restore time breaks SLA.</p>



<p>The TwinDB Backup Tool supports verification starting from version 2.15.0 which you can install from the <a href="https://github.com/twindb/backup">source code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/verify-mysql-backups-with-twindb-backup-tool/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Recover Table Structure From InnoDB Dictionary</title>
		<link>https://twindb.com/recover-table-structure-from-innodb-dictionary-recover-table-structure-from-innodb-dictionary/</link>
					<comments>https://twindb.com/recover-table-structure-from-innodb-dictionary-recover-table-structure-from-innodb-dictionary/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Fri, 18 Sep 2020 15:34:32 +0000</pubDate>
				<category><![CDATA[MySQL Data Recovery]]></category>
		<category><![CDATA[DROP DATABASE]]></category>
		<category><![CDATA[DROP TABLE]]></category>
		<category><![CDATA[innodb dictionary]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=281</guid>

					<description><![CDATA[When a table gets dropped, MySQL removes the respective .frm file. This post explains how to recover the table structure if the table was dropped. You need the table structure to recover a dropped table from the InnoDB tablespace. The B+tree structure of the InnoDB index doesn’t contain any information about field types. MySQL needs&#8230; <a class="more-link" href="https://twindb.com/recover-table-structure-from-innodb-dictionary-recover-table-structure-from-innodb-dictionary/">Continue reading <span class="screen-reader-text">Recover Table Structure From InnoDB Dictionary</span></a>]]></description>
										<content:encoded><![CDATA[
<p>When a table gets dropped, MySQL removes the respective .frm file. This post explains how to recover the table structure if the table was dropped.</p>



<p>You need the table structure to recover a dropped table from the InnoDB tablespace. The B+tree structure of the InnoDB index doesn’t contain any information about field types. MySQL needs to know that in order to access records of the InnoDB table. Normally, MySQL gets the table structure from the .frm file. But when MySQL drops a table the respective frm file removed too.</p>



<p>Fortunately, there’s one more place where MySQL keeps the table structure. It’s the InnoDB dictionary.</p>



<p>The InnoDB dictionary is a set of tables where InnoDB keeps information about the tables. I reviewed them in detail in a separate <a rel="noreferrer noopener" href="https://twindb.com/innodb-dictionary/" target="_blank" data-type="post" data-id="73">InnoDB Dictionary</a> post earlier. After the DROP, InnoDB deletes records related to the dropped table from the dictionary. So, we need to recover deleted records from the dictionary and then get the table structure.</p>



<h2>Compiling Data Recovery Tool</h2>



<p>First, we need to get the source code. The code is hosted on <a href="https://github.com/twindb/undrop-for-innodb">GitHub</a>.</p>



<pre class="wp-block-preformatted">git clone <a href="https://github.com/twindb/undrop-for-innodb.git">https://github.com/twindb/undrop-for-innodb.git</a></pre>



<p>To compile it, we need gcc, bison and flex. Install these packages with a package manager (yum/apt/etc). Then, time to compile.</p>



<pre class="wp-block-preformatted"># make
cc -g -O3 -I./include -c stream_parser.c
cc -g -O3 -I./include&nbsp; -pthread -lm stream_parser.o -o stream_parser
flex&nbsp; sql_parser.l
bison&nbsp; -o sql_parser.c sql_parser.y
sql_parser.y: conflicts: 6 shift/reduce
cc -g -O3 -I./include -c sql_parser.c
cc -g -O3 -I./include -c c_parser.c
cc -g -O3 -I./include -c tables_dict.c
cc -g -O3 -I./include -c print_data.c
cc -g -O3 -I./include -c check_data.c
cc -g -O3 -I./include&nbsp; sql_parser.o c_parser.o tables_dict.o print_data.o check_data.o -o c_parser -pthread -lm
cc -g -O3 -I./include -o innochecksum_changer innochecksum.c</pre>



<h2>Recover The InnoDB Dictionary</h2>



<p>Now, let’s create dictionary tables in the sakila_recovered database. The data recovery tool comes with the structure of the dictionary tables.</p>



<pre class="wp-block-preformatted"># cat dictionary/SYS_* | mysql sakila_recovered</pre>



<p>The dictionary is stored in the ibdata1 file. So, let’s parse it.</p>



<pre class="wp-block-preformatted">./stream_parser -f /var/lib/mysql/ibdata1
&nbsp;
...
&nbsp;
Size to process:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 79691776 (76.000 MiB)
Worker(0): 84.13% done. 2014-09-03 16:31:20 ETA(in 00:00:00). Processing speed: 7.984 MiB/sec
Worker(2): 84.21% done. 2014-09-03 16:31:20 ETA(in 00:00:00). Processing speed: 8.000 MiB/sec
Worker(1): 84.21% done. 2014-09-03 16:31:21 ETA(in 00:00:00). Processing speed: 4.000 MiB/sec
All workers finished in 2 sec</pre>



<p>Now, we need to extract the dictionary records from the InnoDB pages. Let’s create a directory for the table dumps.</p>



<pre class="wp-block-preformatted"># mkdir -p dumps/default</pre>



<p>And now we can generate table dumps and <code>LOAD INFILE</code> commands to load the dumps. We also need to specify the <code>-D</code> option to the <code>c_parser</code> because the records we need were deleted from the dictionary when the table was dropped.</p>



<h3>SYS_TABLES</h3>



<pre class="wp-block-preformatted"># ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page \
&nbsp;&nbsp;&nbsp;&nbsp;-t dictionary/SYS_TABLES.sql \
&nbsp;&nbsp;&nbsp; &gt; dumps/default/SYS_TABLES \
&nbsp;&nbsp;&nbsp;&nbsp;2&gt; dumps/default/SYS_TABLES.sql</pre>



<h3>SYS_INDEXES</h3>



<pre class="wp-block-preformatted"># ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page \
&nbsp;&nbsp;&nbsp;&nbsp;-t dictionary/SYS_INDEXES.sql \
&nbsp;&nbsp;&nbsp; &gt; dumps/default/SYS_INDEXES \
&nbsp;&nbsp;&nbsp;&nbsp;2&gt; dumps/default/SYS_INDEXES.sql</pre>



<h3>SYS_COLUMNS</h3>



<pre class="wp-block-preformatted"># ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000002.page \
&nbsp;&nbsp;&nbsp;&nbsp;-t dictionary/SYS_COLUMNS.sql \
&nbsp;&nbsp;&nbsp; &gt; dumps/default/SYS_COLUMNS \
&nbsp;&nbsp;&nbsp;&nbsp;2&gt; dumps/default/SYS_COLUMNS.sql</pre>



<h3>SYS_FIELDS</h3>



<pre class="wp-block-preformatted"># ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000004.page \
&nbsp;&nbsp;&nbsp;&nbsp;-t dictionary/SYS_FIELDS.sql \
&nbsp;&nbsp;&nbsp; &gt; dumps/default/SYS_FIELDS \
&nbsp;&nbsp;&nbsp;&nbsp;2&gt; dumps/default/SYS_FIELDS.sql</pre>



<p>With the generated <code>LOAD INFILE</code> commands it’s easy to load the dumps.</p>



<pre class="wp-block-preformatted"># cat dumps/default/*.sql | mysql sakila_recovered</pre>



<p>Now we have the InnoDB dictionary loaded into normal InnoDB tables.</p>



<h2>Compiling sys_parser</h2>



<p>ys_parser is a tool that reads the dictionary from tables stored in MySQL and generates the CREATE TABLE structure for a table.</p>



<p>To compile it we need MySQL libraries and development files. Depending on distribution, they may be in -devel or -dev package. On RedHat based systems, you can check it with the command yum provides “*/mysql_config” . On my server it was the mysql-community-devel package.</p>



<p>If all necessary packages are installed, the compilation boils down to a simple command:</p>



<pre class="wp-block-preformatted"># make sys_parser
/usr/bin/mysql_config
cc `mysql_config --cflags` `mysql_config --libs` -o sys_parser sys_parser.c</pre>



<h2>Recover Table Structure</h2>



<p>Now <code>sys_parser</code> can do its magic. Just run it to get the <code>CREATE</code> statement in the standard output.</p>



<pre class="wp-block-preformatted"># ./sys_parser
sys_parser [-h &lt;host&gt;] [-u &lt;user&gt;] [-p &lt;passowrd&gt;] [-d &lt;db&gt;] databases/table</pre>



<p>It will use &#8220;root&#8221; as the username to connect to MySQL, &#8220;querty&#8221; &#8211; as the password. The dictionary is stored in <code>SYS_*</code> tables in the <code>sakila_recovered</code> database. What we want to recover is <code>sakila.actor</code>. InnoDB uses a slash &#8220;/&#8221; as a separator between database name and table name, so does <code>sys_parser</code>.</p>



<pre class="wp-block-preformatted"># ./sys_parser -u root -p qwerty&nbsp; -d sakila_recovered sakila/actor
CREATE TABLE `actor`(
`actor_id` SMALLINT UNSIGNED NOT NULL,
`first_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
`last_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
`last_update` TIMESTAMP NOT NULL,
PRIMARY KEY (`actor_id`)
) ENGINE=InnoDB;</pre>



<pre class="wp-block-preformatted"># ./sys_parser -u root -p qwerty&nbsp; -d sakila_recovered sakila/customer
CREATE TABLE `customer`(
`customer_id` SMALLINT UNSIGNED NOT NULL,
`store_id` TINYINT UNSIGNED NOT NULL,
`first_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
`last_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL,
`email` VARCHAR(50) CHARACTER SET 'utf8' COLLATE 'utf8_general_ci',
`address_id` SMALLINT UNSIGNED NOT NULL,
`active` TINYINT NOT NULL,
`create_date` DATETIME NOT NULL,
`last_update` TIMESTAMP NOT NULL,
PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB;</pre>



<p>There are few caveats though.</p>



<ol><li>InnoDB doesn’t store all information you can find in the <code>.frm</code> file. For example, if a field is <code>AUTO_INCREMENT</code>, the InnoDB dictionary knows nothing about it. Therefore, <code>sys_parser</code> won’t recover that property. If there were any field or table level comments, they’ll be lost.</li><li><code>sys_parser</code> generates the table structure eligible for further data recovery. It could but it doesn’t recover secondary indexes, or foreign keys.</li><li>InnoDB stores the <code>DECIMAL</code> type as a binary string. It doesn’t store the precision of a <code>DECIMAL</code> field. So, that information will be lost.</li></ol>



<p>For example, table payment uses DECIMAL to store money.</p>



<pre class="wp-block-preformatted"># ./sys_parser -u root -p qwerty&nbsp;&nbsp;-d sakila_recovered sakila/payment
CREATE TABLE `payment`(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`payment_id` SMALLINT UNSIGNED NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`customer_id` SMALLINT UNSIGNED NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`staff_id` TINYINT UNSIGNED NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`rental_id` INT,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`amount` DECIMAL(6,0) NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`payment_date` DATETIME NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`last_update` TIMESTAMP NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PRIMARY KEY (`payment_id`)
) ENGINE=InnoDB;</pre>



<p>Fortunately, Oracle is planning to <a rel="noreferrer noopener" href="http://www.tocker.ca/2014/07/30/beyond-the-frm-ideas-for-a-native-mysql-data-dictionary.html" target="_blank">extend the InnoDB dictionary and finally get rid of .frm files</a>. I salute that decision, having the structure in two places leads to inconsistencies.</p>



<p>Image credit: <a href="https://www.flickr.com/photos/khajoo/">m.khajoo</a></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/recover-table-structure-from-innodb-dictionary-recover-table-structure-from-innodb-dictionary/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Simple shell script is bad idea</title>
		<link>https://twindb.com/simple-shell-script-is-bad-idea/</link>
					<comments>https://twindb.com/simple-shell-script-is-bad-idea/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Tue, 15 Sep 2020 00:26:57 +0000</pubDate>
				<category><![CDATA[Infrastructure As Code]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=276</guid>

					<description><![CDATA[Internet is full of simple shell backup scripts. Every DBA and system administrators (including myself) has written one. Bash is a bad tool for this job and in this post I will explain why. Let’s find out why the need to write anything arises at first place. There is a lot of great backup tools&#8230; <a class="more-link" href="https://twindb.com/simple-shell-script-is-bad-idea/">Continue reading <span class="screen-reader-text">Simple shell script is bad idea</span></a>]]></description>
										<content:encoded><![CDATA[
<p>Internet is full of simple shell backup scripts. Every DBA and system administrators (including myself) has written one. Bash is a bad tool for this job and in this post I will explain why.</p>



<p>Let’s find out why the need to write anything arises at first place. There is a lot of great backup tools for databases as well as for files. Following Unix paradigm they do one thing and do it well. With <a href="https://www.gnu.org/software/tar/" target="_blank" rel="noreferrer noopener">tar</a>&nbsp;I can copy files, with <a href="https://dev.mysql.com/doc/en/mysqldump.html" target="_blank" rel="noreferrer noopener">mysqldump</a> I can copy MySQL database. But there are other problems to solve. How to do scheduling (although it’s the easiest one), encryption (both transfers and at rest),&nbsp; compression, backups retention, work with storage. Surely, I’ve missed many more smaller but important details. That’s exactly a reason why people write wrappers on top of their favorite backup tools.</p>



<p>Don’t you think it’s too much for a shell script?</p>



<p>At the beginning I also needed something quick and dirty to backup our databases and files. In one evening I wrote a simple script (!) for MySQL and for files. It was a bash script, its config was also a bash piece. I put it in cron and was content for the moment.</p>



<p>Then I tried to reuse the script for our customers. The script wasn’t good for them in that state. The databases were bigger, they needed S3 storage, incremental backups and other features. That was a moment when the simplicity fired back.</p>



<p>First of all, the shell script has zero test coverage. There were neither unit not integration tests. If you add a smallest change there is no way to guarantee that something else won’t break. Same with bug fixes. If you fix a bug once there is no way to make sure the bug won’t appear again. I’m not aware of any unit test frameworks for Bash, never seen anybody using them. That’s why Bash is a good tool for stuff like “do step 1; do step 2; do step 3; exit”. Anything more complex than that is a reckless driving without fastened seatbelt – if you skilled enough you’ll get to point B, but if anything goes wrong, consequences are catastrophic.</p>



<p>Another problem with Bash is lack of means to work with data. Everything that can be elegantly organized in Python classes becomes spaghetti code in Bash. For any complex algorithm it’s vitally important.</p>



<p>Error handling in Bash is limited and tricky. The best what it has is <code>set -o errexit</code> . And I hope you learned about&nbsp; <code>set -o pipefail</code> not the hard way like I did :).</p>



<p>Instead of conclusion I want to tell that vast majority of our data recovery cases has similar story. There was a home baked script that wasn’t running/silently failing/buggy. When the backup copy became needed the script failed to deliver.</p>



<p>That happens because backup is not a simple problem and it cannot be solved by a simple script.</p>



<p>P.S.</p>



<p>I’m writing this post looking at a tower (<a href="https://it.wikipedia.org/wiki/Torre_Grossa" target="_blank" rel="noreferrer noopener">Torre Grossa</a>) built &nbsp;in 14th century. It wouldn’t survive time if ancient masters didn’t follow best practices.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/simple-shell-script-is-bad-idea/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Recovering A Corrupt MySQL Database</title>
		<link>https://twindb.com/recover-corrupt-mysql-database/</link>
					<comments>https://twindb.com/recover-corrupt-mysql-database/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Mon, 14 Sep 2020 23:48:51 +0000</pubDate>
				<category><![CDATA[MySQL Data Recovery]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=271</guid>

					<description><![CDATA[Author Andriy Lysyuk. The unDROP for InnoDB tool can be used to recover corrupt MySQL databases. In this post, we will show how to repair a MySQL database if its files got corrupted and even innodb_force_recovery=6 doesn’t help. The corruption of InnoDB tablespace may be caused by many reasons. A dying hard drive can write&#8230; <a class="more-link" href="https://twindb.com/recover-corrupt-mysql-database/">Continue reading <span class="screen-reader-text">Recovering A Corrupt MySQL Database</span></a>]]></description>
										<content:encoded><![CDATA[
<p></p>



<p><em>Author <a href="https://www.linkedin.com/in/lysyuk/">Andriy Lysyuk</a>.</em></p>



<p></p>



<div class="wp-block-media-text alignwide is-stacked-on-mobile is-vertically-aligned-top"><figure class="wp-block-media-text__media"><img loading="lazy" width="661" height="577" src="https://twindb.com/wp-content/uploads/2014/08/Corruption.gif" alt="" class="wp-image-119"/></figure><div class="wp-block-media-text__content">
<p><a href="https://twindb.com/undrop-for-innodb/" target="_blank" rel="noreferrer noopener">The unDROP for InnoDB</a> tool can be used to recover corrupt MySQL databases. In this post, we will show how to repair a MySQL database if its files got corrupted and even <code>innodb_force_recovery=6</code> doesn’t help.</p>
</div></div>



<p>The corruption of InnoDB tablespace may be caused by many reasons. A dying hard drive can write garbage, thus, page checksum will be wrong. InnoDB then reports to the error log:</p>



<pre class="wp-block-preformatted">InnoDB: Database page corruption on disk or a failed
InnoDB: file read of page 4.</pre>



<p>MySQL is well known for its poor start-up script. A simple upgrade procedure may end up with two mysqld processes writing to the same tablespace. That leads to corruption too. Sometimes, power reset corrupts not only the InnoDB files, but the whole file system becomes unusable for the operating system.</p>



<p>InnoDB is very strict when it works with pages. If checksum doesn’t match or some field in the header carries and unexpected value, InnoDB wisely prefers to crash to avoid further corruption.</p>



<p>The manual suggests to start MySQL with <code>innodb_force_recovery</code> option. The purpose of this option is to let user dump their data. There are no means to repair the tablespace. Users must drop the tablespace, create a new one and load the data back.</p>



<p><code>innodb_force_recovery</code> accepts values from one to six. <a href="http://dev.mysql.com/doc/refman/5.6/en/forcing-innodb-recovery.html" target="_blank" rel="noreferrer noopener">The higher value, the more tests InnoDB disables</a>.</p>



<p>In this post, we will assume that MySQL cannot start even with <code>innodb_force_recovery=6</code>.</p>



<p>The recovery toolkit works directly with the InnoDB files, it can read records from the InnoDB page. If some part of the page is damaged, it will just skip that piece and continue to read the records further in the page.</p>



<p>So, let’s corrupt some InnoDB file and recover the table.</p>



<h1>InnoDB Corruption</h1>



<p>For the sake of simplicity we will overwrite part of a&nbsp;<code>.ibd</code> file in the area with user data.<br>In real life, the corruption may be at any place of index PRIMARY.<br>In the middle of the <code>PRIMARY</code> index of the&nbsp;<code>sakila.actor</code>&nbsp;table, we will rewrite the data with 128 &#8220;A&#8221; characters:</p>



<pre class="wp-block-preformatted">0000C058&nbsp;&nbsp;00 00 00 02 00 32 01 00 02 00 1C 69 6E 66 69 6D 75 6D 00 05 .....2.....infimum..
0000C06C&nbsp;&nbsp;00 0B 00 00 73 75 70 72 65 6D 75 6D 07 08 00 00 10 00 29 00 ....supremum......).
0000C080&nbsp;&nbsp;01 00 00 00 00 05 1E 9F 00 00 01 4D 01 10 50 45 4E 45 4C 4F ...........M..PENELO
0000C094&nbsp;&nbsp;50 45 47 55 49 4E 45 53 53 43 F2 F5 A9 08 04 00 00 18 00 26 PEGUINESSC.........&amp;amp;
0000C0A8&nbsp;&nbsp;00 02 00 00 00 00 05 1E 9F 00 00 01 4D 01 1A 4E 49 43 4B 57 ............M..NICKW
0000C0BC&nbsp;&nbsp;41 48 4C 42 45 52 47 43 F2 F5 A9 05 02 00 00 20 00 21 00 03 AHLBERGC....... .!..
0000C0D0&nbsp;&nbsp;00 00 00 00 05 1E 9F 00 00 01 4D 01 24 45 44 43 48 41 53 45 ..........M.$EDCHASE
0000C0E4&nbsp;&nbsp;43 F2 F5 A9 05 08 04 00 28 00 27 00 04 00 00 00 00 05 1E 9F C.......(.'.........
0000C0F8&nbsp;&nbsp;00 00 01 4D 01 2E 4A 45 4E 4E 49 46 45 52 44 41 56 49 53 43 ...M..JENNIFERDAVISC
0000C10C&nbsp;&nbsp;F2 F5 A9 0C 06 00 00 30 00 2C 00 05 00 00 00 00 05 1E 9F 00 .......0.,..........
0000C120&nbsp;&nbsp;00 01 4D 01 38 4A 4F 48 4E 4E 59 4C 4F 4C 4C 4F 42 52 49 47 ..M.8JOHNNYLOLLOBRIG
0000C134&nbsp;&nbsp;49 44 41 43 F2 F5 A9 09 05 00 00 38 00 28 00 06 00 00 00 00 IDAC.......8.(......
0000C148&nbsp;&nbsp;05 1E 9F 00 00 01 41 41 41 41 41 41 41 41 41 41 41 41 41 41 ......AAAAAAAAAAAAAA
0000C15C&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAAAA
0000C170&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAAAA
0000C184&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAAAA
0000C198&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAAAA
0000C1AC&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAAAA
0000C1C0&nbsp;&nbsp;41 41 41 41 41 41 41 41 41 41 41 41 41 41 4E 4B 43 F2 F5 A9 AAAAAAAAAAAAAANKC...
0000C1D4&nbsp;&nbsp;05 09 00 00 58 00 28 00 0A 00 00 00 00 05 1E 9F 00 00 01 4D ....X.(............M
0000C1E8&nbsp;&nbsp;01 6A 43 48 52 49 53 54 49 41 4E 47 41 42 4C 45 43 F2 F5 A9 .jCHRISTIANGABLEC...
0000C1FC&nbsp;&nbsp;04 04 00 00 60 00 22 00 0B 00 00 00 00 05 1E 9F 00 00 01 4D ....`."............M</pre>



<h1>Corrupted InnoDB Table Crashes MySQL</h1>



<p>When MySQL reads a page with the user data, checksum is wrong and the server crashes.</p>



<pre class="wp-block-preformatted">mysql&gt; SELECT COUNT(*) FROM sakila.actor
+----------+
| COUNT(*) |
+----------+
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;200 |
+----------+
&nbsp;
ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query</pre>



<p>Before the crash, MySQL writes to the error log what exactly went wrong and dumps the faulty page:</p>



<pre class="wp-block-preformatted">Version: '5.6.19-67.0'&nbsp;&nbsp;socket: '/var/lib/mysql/mysql.sock'&nbsp;&nbsp;port: 3306&nbsp;&nbsp;Percona Server (GPL), Release 67.0, Revision 618
InnoDB: Database page corruption on disk or a failed
InnoDB: file read of page 4.
InnoDB: You may have to recover from a backup.
2014-07-14 20:18:44 7f060bfff700 InnoDB: Page dump in ascii and hex (16384 bytes):
len 16384; hex 1bce9a5000000004ffffffffffffffff0000000026c3095945bf00000000000000000</pre>



<h1>Recovering InnoDB Table Corruption</h1>



<p>When you see corruption in the InnoDB tablespace. the first thing to try is to start MySQL with the innodb_force_recovery option. It makes sense to try all values starting from one to six.</p>



<p>We assume that MySQL doesn’t start even with <code>innodb_force_recovery=6</code> or it does start, but any SELECT crashes it.</p>



<p>The recovery plan is the following:</p>



<ul><li>Split corrupted InnoDB tablespace into pages; sort the pages by type and index_id.</li><li>Fetch records from the PRIMARY index of the table.</li><li>DROP the corrupted table and create a new one.</li><li>Load records back into MySQL.</li></ul>



<p>We would need to parse two tablespaces: ibdata1 and actor.ibd (since option <code>innodb_file_per_table=ON</code>). The InnoDB dictionary is stored in&nbsp;<code>ibdata1</code>, we need it to know the index_id of the <code>PRIMARY</code> index of the&nbsp;<code>sakila.actor</code>&nbsp;table.</p>



<h2>Split Corrupted InnoDB Tablespace</h2>



<pre class="wp-block-preformatted"># ./stream_parser -f /var/lib/mysql/ibdata1
Opening file: /var/lib/mysql/ibdata1
File information:
&nbsp;
ID of device containing file:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;64768
inode number:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8028
protection:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 100660 (regular file)
number of hard links:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1
user ID of owner:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;106
group ID of owner:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 114
device ID (if special file):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0
blocksize for filesystem I/O:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4096
number of blocks allocated:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;36864
time of last access:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1406832698 Thu Jul 31 14:51:38 2014
time of last modification:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1406833058 Thu Jul 31 14:57:38 2014
time of last status change:&nbsp;&nbsp;&nbsp;&nbsp; 1406833058 Thu Jul 31 14:57:38 2014
total size, in bytes:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 18874368 (18.000 MiB)
&nbsp;
Size to process:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;18874368 (18.000 MiB)
All workers finished in 0 sec</pre>



<p>Now <code>actor.ibd</code>‘s turn.</p>



<pre class="wp-block-preformatted"># ./stream_parser -f /var/lib/mysql/sakila/actor.ibd
Opening file: /var/lib/mysql/sakila/actor.ibd
File information:
&nbsp;
ID of device containing file:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;64768
inode number:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8037
protection:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 100660 (regular file)
number of hard links:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1
user ID of owner:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;106
group ID of owner:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 114
device ID (if special file):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0
blocksize for filesystem I/O:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4096
number of blocks allocated:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;224
time of last access:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1406832349 Thu Jul 31 14:45:49 2014
time of last modification:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1406832300 Thu Jul 31 14:45:00 2014
time of last status change:&nbsp;&nbsp;&nbsp;&nbsp; 1406832300 Thu Jul 31 14:45:00 2014
total size, in bytes:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 114688 (112.000 kiB)
&nbsp;
Size to process:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;114688 (112.000 kiB)
All workers finished in 0 sec</pre>



<h2>Recover InnoDB Dictionary</h2>



<p>We need to know the index_id of the <code>PRIMARY</code> index of the&nbsp;<code>sakila.actor</code>&nbsp;table. Learn more <a rel="noreferrer noopener" href="/innodb-dictionary/" target="_blank" data-type="URL" data-id="/innodb-dictionary/">about InnoDB dictionary</a>. Now, we’ll just get index_id of <code>sakila.actor</code>:</p>



<pre class="wp-block-preformatted"># ./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql |grep actor
000000000504&nbsp;&nbsp;&nbsp;&nbsp;85000001320110&nbsp;&nbsp;SYS_TABLES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"sakila/actor"&nbsp;&nbsp;13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ""&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1
00000000050D&nbsp;&nbsp;&nbsp;&nbsp;8E0000013B0110&nbsp;&nbsp;SYS_TABLES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"sakila/film\_actor"&nbsp;&nbsp;&nbsp;&nbsp;20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ""&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8
...
&nbsp;
# ./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql |grep 13
000000000300&nbsp;&nbsp;&nbsp;&nbsp;810000012D01D3&nbsp;&nbsp;SYS_INDEXES&nbsp;&nbsp;&nbsp;&nbsp; 11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"REF\_IND"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 304
000000000504&nbsp;&nbsp;&nbsp;&nbsp;85000001320178&nbsp;&nbsp;SYS_INDEXES&nbsp;&nbsp;&nbsp;&nbsp; 13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"PRIMARY"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3
000000000504&nbsp;&nbsp;&nbsp;&nbsp;850000013201A6&nbsp;&nbsp;SYS_INDEXES&nbsp;&nbsp;&nbsp;&nbsp; 13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"idx\_actor\_last\_name"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4
000000000505&nbsp;&nbsp;&nbsp;&nbsp;860000013301CE&nbsp;&nbsp;SYS_INDEXES&nbsp;&nbsp;&nbsp;&nbsp; 14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"PRIMARY"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3
...</pre>



<p>So, the index_id of the <code>PRIMARY</code> index of the&nbsp;<code>sakila.actor</code> table is 15, the fifth column in the dump.</p>



<h2>Recover Records From The PRIMARY Index Of The Table</h2>



<p><code>c_parser</code> reads InnoDB pages, matches them with a given table structure, and dumps records in a tab-separated values format.</p>



<p>Opposite to InnoDB, when <code>c_parser</code> hits corrupted area it skips it and continues reading the page.</p>



<p>Let’s read the records from index_id 15, which is the <code>PRIMARY</code> index according to the dictionary.</p>



<pre class="wp-block-preformatted"># ./c_parser -6f pages-actor.ibd/FIL_PAGE_INDEX/0000000000000015.page \
    -t sakila/actor.sql \
    > dumps/default/actor
    2> dumps/default/actor_load.sql
 
# cat dumps/default/actor
-- Page id: 3, Format: COMPACT, Records list: Invalid, Expected records: (0 200)
72656D756D07    08000010002900  actor   30064   "\0\0\0\0"      ""      "1972-09-20 23:07:44"
1050454E454C    4F50454755494E  actor   19713   "ESSC▒" ""      "2100-08-09 07:52:36"
00000000051E    9F0000014D011A  actor   2       "NICK"  "WAHLBERG"      "2006-02-15 04:34:33"
00000000051E    9F0000014D0124  actor   3       "ED"    "CHASE" "2006-02-15 04:34:33"
00000000051E    9F0000014D012E  actor   4       "JENNIFER"      "DAVIS" "2006-02-15 04:34:33"
00000000051E    9F0000014D0138  actor   5       "JOHNNY"        "LOLLOBRIGIDA"  "2006-02-15 04:34:33"
00000000051E    9F000001414141  actor   6       "AAAAA" "AAAAAAAAA"     "2004-09-10 01:53:05"
 
00000000051E    9F0000014D016A  actor   10      "CHRISTIAN"     "GABLE" "2006-02-15 04:34:33"
...</pre>



<p>We have identified some valid records, but certainly there’s also some “garbage”. Pay attention to the recovered records before “Nick Wahlberg”. Definitely, there should be records of “Penelope Guiness”, since we haven’t overwritten that data in the <code>actor.ibd</code> file.</p>



<h2>Filters</h2>



<p>We can improve recovery quality by applying filters on possible values of certain fields. There are 200 records on the original table, but the first two “garbage” records have some weird identifiers (30064 and 19713). We know that the actor identifier should be in the range of [1..300]. Therefore, we tell the parser to match that condition. For this purpose, we add a hint in the comments of the&nbsp;<code>actor.sql</code> file that defines actor table. This comment should be in a special format for parser to recognize it. Listing of the part of <code>actor.sql</code> file (note a comma after the comment!):</p>



<pre class="wp-block-preformatted">CREATE TABLE `actor` (
&nbsp;&nbsp;`actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT
&nbsp;&nbsp;&nbsp;&nbsp;/*!FILTER
&nbsp;&nbsp;&nbsp;&nbsp; int_min_val: 1
&nbsp;&nbsp;&nbsp;&nbsp; int_max_val: 300 */,
&nbsp;&nbsp;`first_name` varchar(45) NOT NULL,
&nbsp;&nbsp;`last_name` varchar(45) NOT NULL,
&nbsp;&nbsp;`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
&nbsp;&nbsp;PRIMARY KEY (`actor_id`),
&nbsp;&nbsp;KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8;</pre>



<p>After applying the filter, recognized records look much better:</p>



<pre class="wp-block-preformatted"># ./c_parser -6f pages-actor.ibd/FIL_PAGE_INDEX/0000000000000015.page \
&nbsp;&nbsp;&nbsp;&nbsp;-t sakila/actor.sql \
&nbsp;&nbsp;&nbsp;&nbsp;&amp;gt; dumps/default/actor \
&nbsp;&nbsp;&nbsp;&nbsp;2&amp;gt; dumps/default/actor_load.sql
&nbsp;
# head -10 dumps/default/actor
-- Page id: 3, Format: COMPACT, Records list: Invalid, Expected records: (0 200)
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D0110&nbsp;&nbsp;actor&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "PENELOPE"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"GUINESS"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D011A&nbsp;&nbsp;actor&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "NICK"&nbsp;&nbsp;"WAHLBERG"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D0124&nbsp;&nbsp;actor&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "ED"&nbsp;&nbsp;&nbsp;&nbsp;"CHASE" "2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D012E&nbsp;&nbsp;actor&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "JENNIFER"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"DAVIS" "2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D0138&nbsp;&nbsp;actor&nbsp;&nbsp; 5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "JOHNNY"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"LOLLOBRIGIDA"&nbsp;&nbsp;"2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F000001414141&nbsp;&nbsp;actor&nbsp;&nbsp; 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "AAAAA" "AAAAAAAAA"&nbsp;&nbsp;&nbsp;&nbsp; "2004-09-10 01:53:05"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D016A&nbsp;&nbsp;actor&nbsp;&nbsp; 10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"CHRISTIAN"&nbsp;&nbsp;&nbsp;&nbsp; "GABLE" "2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D0174&nbsp;&nbsp;actor&nbsp;&nbsp; 11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"ZERO"&nbsp;&nbsp;"CAGE"&nbsp;&nbsp;"2006-02-15 04:34:33"
00000000051E&nbsp;&nbsp;&nbsp;&nbsp;9F0000014D017E&nbsp;&nbsp;actor&nbsp;&nbsp; 12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"KARL"&nbsp;&nbsp;"BERRY" "2006-02-15 04:34:33"</pre>



<p>You see, the record for “Penelope Guiness” is already here. The only issue remaining is the invalid record 6-“AAAAA”-“AAAAAAAAA”. This happens because the record appeared to have actor_id of 6, which is what we expected. Ideally, the dump shouldn’t have any junk records, so you may try to add more filters on other fields.<br>Or, we can delete these records in the database manually later.</p>



<h2>DROP Corrupted Tables And Create New One</h2>



<p>As soon as we have dumps of all tables we need to create a new instance of MySQL.</p>



<p>If it’s a single table corruption it makes sense to try <code>innodb_force_recovery=6</code> to DROP the table.</p>



<p>If MySQL can’t even start, try to move the corrupt <code>actor.ibd</code> elsewhere. In the recovery mode, after DROP TABLE actor MySQL removes the record from the dictionary. Remove actor.frm if it still remains.</p>



<p>The point is to get a clean up &amp; running MySQL, ready to import the table dump.</p>



<p>Once MySQL is ready create an empty table actor:</p>



<pre class="wp-block-preformatted">mysql&gt; CREATE TABLE `actor` (
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp; `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp; `first_name` varchar(45) NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp; `last_name` varchar(45) NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp; `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PRIMARY KEY (`actor_id`),
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;KEY `idx_actor_last_name` (`last_name`)
&nbsp;&nbsp;&nbsp;&nbsp;-&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
Query OK, 0 rows affected (0.01 sec)</pre>



<h2>Load Records Back Into MySQL</h2>



<p>Then, we load information from the recovered dump:</p>



<pre class="wp-block-preformatted"># mysql --local-infile -uroot -p$mypass
Welcome to the MySQL monitor.&nbsp;&nbsp;Commands end with ; or \g.
...
mysql&gt; use sakila;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
&nbsp;
Database changed
mysql&gt; source dumps/default/actor_load.sql
Query OK, 0 rows affected (0.00 sec)
&nbsp;
Query OK, 199 rows affected, 1 warning (0.00 sec)
Records: 198&nbsp;&nbsp;Deleted: 1&nbsp;&nbsp;Skipped: 0&nbsp;&nbsp;Warnings: 1</pre>



<p>The final step is to know how much data we’ve lost due to corruption.<br>The c_parser provides count of expected and actually found records.<br>In the beginning of each page it gives number of expected records:</p>



<pre class="wp-block-preformatted">-- Page id: 3, Format: COMPACT, Records list: Invalid, Expected records: (0 200)</pre>



<p>Which means 200 records are expected, but the list of records is broken (thus, Records list: Invalid).</p>



<p>In the end of each page it gives a summary of what was actually found.</p>



<pre class="wp-block-preformatted">-- Page id: 3, Found records: 197, Lost records: YES, Leaf page: YES</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/recover-corrupt-mysql-database/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Show Differences Found by pt-table-checksum</title>
		<link>https://twindb.com/show-differences-found-by-pt-table-checksum/</link>
					<comments>https://twindb.com/show-differences-found-by-pt-table-checksum/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Sun, 13 Sep 2020 22:35:48 +0000</pubDate>
				<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=242</guid>

					<description><![CDATA[First of all, pt-table-checksum&#160;is perfect for checking if the master and its slaves are in-sync. It answers the question “Are the slaves consistent with the&#160;master?”. However, if they’re not, pt-table-checksum doesn’t actually tell you what exactly is wrong. master# pt-table-checksum -q TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 09-03T22:21:10 0 1 2 1 0&#8230; <a class="more-link" href="https://twindb.com/show-differences-found-by-pt-table-checksum/">Continue reading <span class="screen-reader-text">Show Differences Found by pt-table-checksum</span></a>]]></description>
										<content:encoded><![CDATA[
<p>First of all, <code>pt-table-checksum</code>&nbsp;is perfect for checking if the master and its slaves are in-sync. It answers the question “Are the slaves consistent with the&nbsp;master?”. However, if they’re not, pt-table-checksum doesn’t actually tell you what exactly is wrong.</p>



<pre class="wp-block-preformatted">master# pt-table-checksum -q
TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE
09-03T22:21:10 0 1 2 1 0 0.013 mysql.proxies_priv
09-03T22:21:10 0 1 9 1 0 0.010 mysql.user</pre>



<h2>From <code>pt-table-checksum</code>&nbsp;to&nbsp;<code>pt-table-sync</code></h2>



<p>Nevertheless, <code>pt-table-sync</code> may give you a partial answer. It can print SQL statements to&nbsp;sync the replication cluster. Thus, reading the SQL code, you may guess what records were missing/extra or differ.</p>



<pre class="wp-block-preformatted">master# pt-table-sync --print --replicate percona.checksums localhost
DELETE FROM `mysql`.`proxies_priv` WHERE `host`='slave.box' AND `user`='root' AND `proxied_host`='' AND `proxied_user`='' LIMIT 1 /*percona-toolkit src_db:mysql src_tbl:proxies_priv src_dsn:h=localhost dst_db:mysql dst_tbl:proxies_priv dst_dsn:h=ip-192-168-35-251.ec2.internal lock:1 transaction:0 changing_src:percona.checksums replicate:percona.checksums bidirectional:0 pid:21261 user:root host:master.box*/;
REPLACE INTO `mysql`.`proxies_priv`(`host`, `user`, `proxied_host`, `proxied_user`, `with_grant`, `grantor`, `timestamp`) VALUES ('localhost', 'root', '', '', '1', '', '2016-09-03 20:02:28') /*percona-toolkit src_db:mysql src_tbl:proxies_priv src_dsn:h=localhost dst_db:mysql dst_tbl:proxies_priv dst_dsn:h=ip-192-168-35-251.ec2.internal lock:1 transaction:0 changing_src:percona.checksums replicate:percona.checksums bidirectional:0 pid:21261 user:root host:master.box*/;
REPLACE INTO `mysql`.`proxies_priv`(`host`, `user`, `proxied_host`, `proxied_user`, `with_grant`, `grantor`, `timestamp`) VALUES ('master.box', 'root', '', '', '1', '', '2016-09-03 20:02:28') /*percona-toolkit src_db:mysql src_tbl:proxies_priv src_dsn:h=localhost dst_db:mysql dst_tbl:proxies_priv dst_dsn:h=ip-192-168-35-251.ec2.internal lock:1 transaction:0 changing_src:percona.checksums replicate:percona.checksums bidirectional:0 pid:21261 user:root host:master.box*/;
...</pre>



<p>This is not very convenient. I’d note that&nbsp;<code>pt-table-sync</code> serves its main purpose – syncing two or more servers. It was never a goal to show you inconsistencies between two servers. Which is something you&#8217;d like to know.</p>



<h2>Using TwinDB Table Compare</h2>



<p>TwinDB Table Compare is a tool that shows the exact difference between a slave and its master. It’s published on <a rel="noreferrer noopener" href="https://pypi.python.org/pypi/twindb-table-compare" target="_blank">PyPi</a>, therefore, you can install it right away:</p>



<pre class="wp-block-preformatted">slave# pip install twindb-table-compare</pre>



<p>Old versions of <code>twindb-table-compare</code> used MySQLDb, which depended on libmysql. Not anymore. Now it uses pure Python implementation <code>pymysql</code>. A little convenience for users.</p>



<p>TwinDB Table Compare works side-by-side with pt-table-checksum. You know, pt-table-checksum runs against the master and populates table <code>percona.checksums</code>.</p>



<p><code>twindb-table-compare</code> reads mismatched chunks from <code>percona.checksums</code>, then runs a SELECT against the master and the slave. Then compares the result and shows it to a user as a diff.</p>



<p>Let&#8217;s see <code>twindb-table-compare</code> in action. To illustrate how it works, I intentionally deleted a record directly from the slave.</p>



<pre class="wp-block-preformatted">slave&gt; delete from t1 where id = 393188;</pre>



<p>Then, I ran pt-table-checksum on the master, that found an inconsistency. To check if there are any mismatching chunks you can run a query against <code>percona.checksums</code>.</p>



<pre class="wp-block-preformatted">slave# mysql -e "SELECT * FROM percona.checksums WHERE this_cnt&lt;&gt;master_cnt OR this_crc&lt;&gt;master_crc AND db='test'\G"
*************************** 1. row ***************************
            db: test
           tbl: t1
         chunk: 3
    chunk_time: 0.202156
   chunk_index: PRIMARY
lower_boundary: 319858
upper_boundary: 393197
      this_crc: 376517ca
      this_cnt: 73339
    master_crc: 0
    master_cnt: 73340
            ts: 2020-09-13 22:31:28</pre>



<p>Now, that we know there is an inconsistency, we need to know what exactly it is.</p>



<pre class="wp-block-preformatted">slave# twindb-table-compare --user dba --password qwerty
...
2020-09-13 22:33:47,642: INFO: compare.get_inconsistencies():543: Executing: SELECT chunk FROM percona.checksums WHERE (this_crc<>master_crc OR this_cnt<>master_cnt) AND db='test' AND tbl='t1'
2020-09-13 22:33:47,643: INFO: compare.get_inconsistencies():554: Found 1 inconsistent chunk
2020-09-13 22:33:47,643: INFO: compare.build_chunk_query():360: # test.t1, chunk 3
2020-09-13 22:33:47,643: INFO: compare.get_chunk_index():63: Executing SELECT chunk_index FROM percona.checksums WHERE db='test' AND tbl='t1' AND chunk = 3
2020-09-13 22:33:47,700: INFO: compare.build_chunk_query():362: # chunk index: PRIMARY
2020-09-13 22:33:47,700: INFO: compare.get_index_fields():86: Executing SELECT COLUMN_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='test' AND TABLE_NAME='t1' AND INDEX_NAME='PRIMARY' ORDER BY SEQ_IN_INDEX
2020-09-13 22:33:47,702: INFO: compare.get_boundary():129: Executing SELECT lower_boundary, upper_boundary FROM percona.checksums WHERE db='test' AND tbl='t1' AND chunk = 3
2020-09-13 22:33:47,705: INFO: compare.print_horizontal():402: Executing: SELECT id, name FROM test.t1 USE INDEX (PRIMARY) WHERE ( 0 OR ( 1 AND id >= '319858' ) ) AND ( 0 OR ( 1 AND id <= '393197' ) ) 2020-09-13 22:33:52,175: INFO: compare.print_horizontal():424: Executing: SELECT id, name FROM test.t1 USE INDEX (PRIMARY) WHERE ( 0 OR ( 1 AND id >= '319858' ) ) AND ( 0 OR ( 1 AND id <= '393197' ) )
2020-09-13 22:33:56,944: INFO: compare.get_inconsistencies():566: Differences between slave localhost and its master:
@@ -73328,7 +73328,6 @@
393185 40.79178826957176
393186 40.79178826957176
393187 40.79178826957176
-393188 40.79178826957176
393189 40.79178826957176
393190 40.79178826957176
393191 40.79178826957176</pre>



<p>From the output we see that the slave is missing a record with id 393188 - the one I deleted.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/show-differences-found-by-pt-table-checksum/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Efficient Use Of Indexes In MySQL</title>
		<link>https://twindb.com/efficient-use-of-indexes-in-mysql/</link>
					<comments>https://twindb.com/efficient-use-of-indexes-in-mysql/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Sun, 13 Sep 2020 17:41:55 +0000</pubDate>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[innodb]]></category>
		<category><![CDATA[query optimization]]></category>
		<guid isPermaLink="false">https://twindb.com/?p=114</guid>

					<description><![CDATA[These are the slides of the “Efficient Use Of Indexes In MySQL” talk we delivered on the&#160;SFMySQL Meetup. This is an introductory talk for developers on MySQL indexes. In my opinion, it’s quite important to understand how InnoDB organizes data. If you know how MySQL accesses data, it’s easier to write optimal queries. When working&#8230; <a class="more-link" href="https://twindb.com/efficient-use-of-indexes-in-mysql/">Continue reading <span class="screen-reader-text">Efficient Use Of Indexes In MySQL</span></a>]]></description>
										<content:encoded><![CDATA[
<p>These are the slides of the “Efficient Use Of Indexes In MySQL” talk we delivered on the&nbsp;<a href="http://www.meetup.com/sf-mysql/events/223025164/" target="_blank" rel="noreferrer noopener">SFMySQL Meetup</a>.</p>



<p>This is an introductory talk for developers on MySQL indexes. In my opinion, it’s quite important to understand how InnoDB organizes data. If you know how MySQL accesses data, it’s easier to write optimal queries.</p>



<p>When working with queries, I imagine secondary indexes as a table with records sorted by secondary key fields. This is a powerful concept that helps to understand the MySQL logic. It’s also easy to understand complex optimizations like <a href="https://dev.mysql.com/doc/refman/5.6/en/group-by-optimization.html#loose-index-scan" target="_blank" rel="noreferrer noopener">loose index scan</a>.</p>



<p>For example, for index (last_name, rank) the secondary index table looks like:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><a href="file:///Users/aleks/Documents/www.twindb.com/twindb.com/wp-content/uploads/2015/06/Screenshot-2015-06-25-14.49.11.png"><img loading="lazy" width="896" height="594" src="https://twindb.com/wp-content/uploads/2015/06/Screenshot-2015-06-25-14.49.11.png" alt="" class="wp-image-135" srcset="https://twindb.com/wp-content/uploads/2015/06/Screenshot-2015-06-25-14.49.11.png 896w, https://twindb.com/wp-content/uploads/2015/06/Screenshot-2015-06-25-14.49.11-300x199.png 300w, https://twindb.com/wp-content/uploads/2015/06/Screenshot-2015-06-25-14.49.11-768x509.png 768w" sizes="(max-width: 896px) 100vw, 896px" /></a></figure></div>



<p>Enjoy the slides!<br></p>



<figure class="wp-block-embed-slideshare wp-block-embed is-type-rich is-provider-slideshare wp-embed-aspect-1-1 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Efficient Use of indexes in MySQL" src="https://www.slideshare.net/slideshow/embed_code/key/hhLtnWkFZomWWR" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/akuzminsky/indexes-in-my-sql-aleksandr-kuzminsky-https-twindbcom" title="Efficient Use of indexes in MySQL" target="_blank">Efficient Use of indexes in MySQL</a> </strong> from <strong><a href="https://www.slideshare.net/akuzminsky" target="_blank">Aleksandr Kuzminsky</a></strong> </div>
</div></figure>



<p>The talk has been recorded. Unfortunately, sound is very quiet. Probably a webinar on the topic will make it up. Let me know if you&#8217;d be interested in the comments below!</p>



<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="How to Efficiently Use Indexes in MySQL" width="750" height="422" src="https://www.youtube.com/embed/ytW1KV_lIbQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/efficient-use-of-indexes-in-mysql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>InnoDB Dictionary</title>
		<link>https://twindb.com/innodb-dictionary/</link>
					<comments>https://twindb.com/innodb-dictionary/#respond</comments>
		
		<dc:creator><![CDATA[aleks]]></dc:creator>
		<pubDate>Sat, 12 Sep 2020 21:23:22 +0000</pubDate>
				<category><![CDATA[MySQL Data Recovery]]></category>
		<category><![CDATA[innodb dictionary]]></category>
		<category><![CDATA[SYS_COLUMNS]]></category>
		<category><![CDATA[SYS_FIELDS]]></category>
		<category><![CDATA[SYS_INDEXES]]></category>
		<guid isPermaLink="false">https://www.twindb.com/?p=73</guid>

					<description><![CDATA[Why Does InnoDB Need A Dictionary An InnoDB dictionary is a set of internal tables InnoDB uses to maintain various information about user tables. It serves as API between a human and the database. While the humans refer to tables by their names, InnoDB works with integer identifiers. The dictionary stores correspondence between table name&#8230; <a class="more-link" href="https://twindb.com/innodb-dictionary/">Continue reading <span class="screen-reader-text">InnoDB Dictionary</span></a>]]></description>
										<content:encoded><![CDATA[
<h1>Why Does InnoDB Need A Dictionary</h1>



<p>An InnoDB dictionary is a set of internal tables InnoDB uses to maintain various information about user tables. It serves as API between a human and the database. While the humans refer to tables by their names, InnoDB works with integer identifiers. The dictionary stores correspondence between table name and index_id.</p>



<p>The dictionary tables are normal InnoDB tables, but they’re not visible to a user. However, some versions of MySQL provide read-only access to the dictionary in information_schema database.</p>



<p>The dictionary is stored in ibdata1. Root page of SYS_TABLES, for example, has id 8, so it’s the eighth page from the beginning of ibdata1.</p>



<p>The dictionary pages are in REDUNDANT format even if you use MySQL 5.6. I hope to write more about record formats in future posts. For now, it’s enough to mention that REDUNDANT is the oldest record format. It was available since 4.0 and was the only format by that time.</p>



<h1>SYS_TABLES</h1>



<pre class="wp-block-preformatted">CREATE TABLE `SYS_TABLES` (
    `NAME` varchar(255) NOT NULL DEFAULT '',
    `ID` bigint(20) unsigned NOT NULL DEFAULT '0',
    `N_COLS` int(10) DEFAULT NULL,
    `TYPE` int(10) unsigned DEFAULT NULL,
    `MIX_ID` bigint(20) unsigned DEFAULT NULL,
    `MIX_LEN` int(10) unsigned DEFAULT NULL,
    `CLUSTER_NAME` varchar(255) DEFAULT NULL,
    `SPACE` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; </pre>



<p>All dictionary tables have predefined hard-coded index_ids. No surprise, as long as InnoDB doesn’t have a dictionary for the dictionary :). SYS_TABLES has index_id 0-1. Root page of SYS_TABLES is page id 8.</p>



<p><strong>NAME</strong> is the human readable table name. Well, the database name and the table name. For example, for table <code>sakila</code>.<code>actor</code> NAME will be “sakila/actor”.</p>



<p><strong>ID</strong> is the table identifier. Just an integer number. The table is referenced to by this table_id in other dictionary tables.</p>



<p><strong>N_COLS</strong> is the number of columns in the table.</p>



<p>I will pretend <strong>TYPE</strong>, <strong>MIX_ID</strong>, <strong>MIX_LEN</strong> and <strong>CLUSTER_NAME</strong> are not important. Actually, I have no idea what these fields are. Anyway, they were never used for data recovery, so let’s leave them for future investigations.</p>



<p><strong>SPACE</strong> is an identifier of a tablespace. Thus ibdata1 is SPACE 0, ibdata2 is SPACE 1, Every ibd file has its own SPACE identifier. SPACE tells in what tablespace the table’s indexes are stored.</p>



<p>Sample record for table sakila.actor: </p>



<pre class="wp-block-preformatted"># ./c_parser \
        -4f pages-1402815586/FIL_PAGE_INDEX/0-1 \
        | grep sakila/actor
...
"sakila/actor" 61 4 1 0 80 "" 47</pre>



<p>“sakila/actor” is the table name obviously. 61 is the table id. 4 is the number of fields in the table: </p>



<pre class="wp-block-preformatted">CREATE TABLE `actor` (
  `actor_id` smallint(5) unsigned NOT NULL 
             AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `last_update` timestamp NOT NULL 
                DEFAULT CURRENT_TIMESTAMP
                ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`actor_id`),
  KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB 
  AUTO_INCREMENT=201 
  DEFAULT CHARSET=utf8 </pre>



<p>47 is the SPACE identifier. It would be 0 if innodb_file_per_table was disabled. It’s not in my environment: </p>



<pre class="wp-block-preformatted"> mysql&gt; show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.00 sec) </pre>



<h1>SYS_INDEXES</h1>



<p>Another important dictionary table is SYS_INDEXES. It stores information about InnoDB indexes.</p>



<p>Its index_id is 0-3 and the root page is stored in page 11. </p>



<pre class="wp-block-preformatted">CREATE TABLE `SYS_INDEXES` (
  `TABLE_ID` bigint(20) unsigned NOT NULL 
             DEFAULT '0',
  `ID` bigint(20) unsigned NOT NULL DEFAULT '0',
  `NAME` varchar(120) DEFAULT NULL,
  `N_FIELDS` int(10) unsigned DEFAULT NULL,
  `TYPE` int(10) unsigned DEFAULT NULL,
  `SPACE` int(10) unsigned DEFAULT NULL,
  `PAGE_NO` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`TABLE_ID`,`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;</pre>



<p>Fields of the table:</p>



<p><strong>TABLE_ID</strong> is the table identifier. It’s one from SYS_TABLES.</p>



<p><strong>ID</strong> is the index identifier. This is a very important field. The matter is, every InnoDB page contains the index_id of the index it belongs to in the header. So, if we want to recover a table, we need to find all pages that belong to the PRIMARY index of the table, which means we need all pages with a specific index_id.</p>



<p><strong>NAME</strong> is the name of an index. Table sakila.actor has two indexes: PRIMARY and idx_actor_last_name. So, these are names in SYS_INDEXES.</p>



<p><strong>N_FIELDS</strong> is the number of fields in an index. idx_actor_last_name has one field, so is it in SYS_INDEXES.</p>



<p><strong>TYPE</strong> is not an important field <img src="https://s.w.org/images/core/emoji/13.1.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p><strong>SPACE</strong> is an InnoDB tablespace identifier showing where the index is stored. Probably, InnoDB developers foresaw the possibility to store indexes in different files, but for current MySQL versions it’s the same as in SYS_TABLES.</p>



<p><strong>PAGE_NO</strong> is an identifier of the root page of the index. As you know, InnoDB stores indexes in <a href="http://en.wikipedia.org/wiki/B%2B_tree">B+tree data structure</a>. Thus, root node of the B+tree is the root page identified by this field.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="471" src="https://twindb.com/wp-content/uploads/2014/06/Bplustree-1024x471.png" alt="" class="wp-image-117" srcset="https://twindb.com/wp-content/uploads/2014/06/Bplustree-1024x471.png 1024w, https://twindb.com/wp-content/uploads/2014/06/Bplustree-300x138.png 300w, https://twindb.com/wp-content/uploads/2014/06/Bplustree-768x354.png 768w, https://twindb.com/wp-content/uploads/2014/06/Bplustree.png 1121w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<h1>SYS_COLUMNS</h1>



<p>SYS_COLUMNS describes individual fields in a table. It is stored as index_id 0-2. </p>



<pre id="block-808c82db-5d9d-46bb-a347-364d444f6dff" class="wp-block-preformatted"> CREATE TABLE `SYS_COLUMNS` (
  `TABLE_ID` bigint(20) unsigned NOT NULL,
  `POS` int(10) unsigned NOT NULL,
  `NAME` varchar(255) DEFAULT NULL,
  `MTYPE` int(10) unsigned DEFAULT NULL,
  `PRTYPE` int(10) unsigned DEFAULT NULL,
  `LEN` int(10) unsigned DEFAULT NULL,
  `PREC` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`TABLE_ID`,`POS`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; </pre>



<p><strong>TABLE_ID</strong> is the table identifier, it comes from SYS_TABLES</p>



<p><strong>POS</strong> is a relative position of the field in the table.</p>



<p><strong>NAME</strong> is the name of the field.</p>



<p><strong>MTYPE</strong> and <strong>PRTYPE</strong> encode the MySQL type of the field. InnoDB started its history as a separate product, not related to MySQL. These two fields are legacy from that time. There’s no strict correspondence between MySQL and the InnoDB type. InnoDB uses its own type that suits a particular MySQL type the best. Encoding and collation are stored here as well.</p>



<p><strong>LEN</strong> is the length of the field.</p>



<p><strong>PREC</strong> is precision for some MySQL types.</p>



<p>Isn’t it interesting to see an example of the table for sakila.actor? Here it goes. Remember, table_id is 61? We know that from SYS_TABLES. </p>



<pre class="wp-block-preformatted">61      0       "actor\_id"     6       1794    2       0
61      1       "first\_name"   12      2162959 135     0
61      2       "last\_name"    12      2162959 135     0
61      3       "last\_update"  3       525575  4       0 </pre>



<p>As we see, smallint is two bytes, timestamp is four bytes. And varchar(45) is 135 bytes. Apparently, this is the maximum size, not the actual size of a field which is different for every record. But why 135, not 45? Because the field is utf8, where each character may use up to three bytes. 3 x 45 adds up to 135.</p>



<h1>SYS_FIELDS</h1>



<p>The last table for today (but not least) is SYS_FIELDS. It’s stored in index_id 0-4.<br>It lists individual fields on all indexes. </p>



<pre class="wp-block-preformatted"> CREATE TABLE `SYS_FIELDS` (
  `INDEX_ID` bigint(20) unsigned NOT NULL,
  `POS` int(10) unsigned NOT NULL,
  `COL_NAME` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`INDEX_ID`,`POS`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; </pre>



<p>The table is quite simple.</p>



<p><strong>INDEX_ID</strong> is the index identifier that we can find in SYS_INDEXES.</p>



<p><strong>POS</strong> is the position of the field in the index.</p>



<p>And <strong>COL_NAME</strong> is the field name.</p>



<h3>Why Do We Care?</h3>



<p>Why do we need an InnoDB dictionary anyway?</p>



<p>There are two reasons. First, if we need to recover table A, we need to know index_id of its PRIMARY index. The easiest way to learn it is the InnoDB dictionary.</p>



<p>Second, as you see, the InnoDB dictionary stores some information about field names and types. Which means, in case we don’t know the table structure we can recover it from the dictionary.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://twindb.com/innodb-dictionary/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/


Served from: twindb.com @ 2021-10-10 14:13:29 by W3 Total Cache
-->