Merge lp:~alikrubin75/undrop-for-innodb/undrop-for-innodb into lp:undrop-for-innodb

Proposed by Alexander Rubin
Status: Needs review
Proposed branch: lp:~alikrubin75/undrop-for-innodb/undrop-for-innodb
Merge into: lp:undrop-for-innodb
Diff against target: 378 lines (+362/-1)
2 files modified
c_parser.c (+1/-1)
recover.sh (+361/-0)
To merge this branch: bzr merge lp:~alikrubin75/undrop-for-innodb/undrop-for-innodb
Reviewer Review Type Date Requested Status
Aleksandr Kuzminsky Needs Fixing
Review via email: mp+226624@code.launchpad.net

Description of the change

I would suggest adding my script recover.sh to the main branch after fixing https://bugs.launchpad.net/undrop-for-innodb/+bug/1339358

To post a comment you must log in.
18. By root <root@mysql-recovery>

New version of recover.sh, merged to v.23

Revision history for this message
Aleksandr Kuzminsky (akuzminsky) wrote :

See inline.
In principle, it's possible a deleted record is still a member of the records list in a page. If so, then the record will be recovered.
Sometimes InnoDB excludes deleted records form the list. I don't know what it depends on, just have seen that before.
So, if check_page() returns OK and the deleted record isn't part of the list it will be silently ignored. If check_page() returns Not OK then c_parser will produce junk records which can be filtered out with c_parser filters. This is better than silently ignoring the deleted records.

review: Needs Fixing

Unmerged revisions

18. By root <root@mysql-recovery>

New version of recover.sh, merged to v.23

17. By EC2 Default User <ec2-user@ip-10-164-164-213>

Merged from trunk, added test_mysql_connection to recover.sh

16. By EC2 Default User <ec2-user@ip-10-164-164-213>

Added recover.sh - simple script to recover the tables.
Modified c_parser.c for fixing https://bugs.launchpad.net/undrop-for-innodb/+bug/1339356 and https://bugs.launchpad.net/undrop-for-innodb/+bug/1339358

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'c_parser.c'
2--- c_parser.c 2014-07-17 22:31:04 +0000
3+++ c_parser.c 2014-09-23 20:59:25 +0000
4@@ -513,7 +513,7 @@
5
6 if(deleted_records_only == 1){
7 if (debug) printf("We look for deleted records only. Consider all pages are not valid\n");
8- return 0;
9+ // return 0;
10 }
11 if (debug) printf("Checking a page\nInfimum offset: 0x%X\nSupremum offset: 0x%X\n", i, s);
12 p_prev = 0;
13
14=== added file 'recover.sh'
15--- recover.sh 1970-01-01 00:00:00 +0000
16+++ recover.sh 2014-09-23 20:59:25 +0000
17@@ -0,0 +1,361 @@
18+#!/bin/bash
19+
20+set -e
21+mysql_user="`whoami`"
22+#mysql_password=""
23+#mysql_host="localhost"
24+MYSQL="mysql "
25+mysql_db=""
26+work_dir="/tmp/recovery_$RANDOM"
27+top_dir="`pwd`"
28+tables=""
29+mysql_tmp_socket="/tmp/mysqldr-$RANDOM.sock"
30+VERBOSE=0
31+USEFRM=0
32+# Defaults
33+Row_format_arg="-5"
34+innodb_file_per_table="ON"
35+innodb_data_file_path='ibdata1'
36+### Functions
37+
38+function usage() {
39+ printf "This tool assumes that a MySQL instance is running. This instance will be used to load the recovered database";
40+ printf "Valid options are \n";
41+ printf " \t -i mysql data d<i>r name; if omited the tool assume you want to recover from the running mysql\n"
42+ printf " \t -d database name to extract \n"
43+ printf " \t -t table_name(s), list table with 'table1 table2 tableN' \n"
44+ printf " \t -v 1 verbose mode \n"
45+ printf " \t -D recover deleted rows \n"
46+ printf " \t -f 1 will use FRMREADER utility to parse FRM files instead of mysqldump. (EXPERIMENTAL) \n"
47+ printf "recover.sh -v 1 -d sakila -t 'film payment' -D"
48+ printf "\n"
49+}
50+
51+function check_mysql_connection {
52+ if [ "`$MYSQL -NB -e 'select 1'`" != "1" ]
53+ then
54+ echo "Can't connect to local mysql. Please add connection information to ~/.my.cnf"
55+ echo "Example: "
56+ echo "[client]"
57+ echo "user=percona"
58+ echo "password=s3cret"
59+ echo ""
60+ #on_error
61+ exit 1
62+ fi
63+ $MYSQL -e "SELECT COUNT(*) FROM user" mysql >/dev/null
64+ has_innodb=`$MYSQL -e "SHOW ENGINES"| grep InnoDB| grep -e "YES" -e "DEFAULT"`
65+ if test -z "$has_innodb"
66+ then
67+ echo "InnoDB is not enabled on this MySQL server"
68+ #on_error
69+ exit 1
70+ fi
71+}
72+
73+function start_temp_mysql {
74+ mysql_temp_datadir="`mktemp -d`"
75+ mysql_start_timeout="300"
76+ #mysql_user="nobody"
77+
78+ mkdir -p "$mysql_temp_datadir"
79+ #chown -R $mysql_user "$mysql_temp_datadir"
80+ mysqld --no-defaults --sql-mode='' --default-storage-engine=MyISAM --default-tmp-storage-engine=MyISAM --skip-innodb --datadir="$mysql_temp_datadir" --socket="$mysql_tmp_socket" --user=$mysql_user --skip-networking --skip-grant-tables 1>/dev/null 2>/dev/null &
81+
82+ while [ "`mysql -NB --socket=\"$mysql_tmp_socket\" -e 'select 1' 2>/dev/null`" != "1" ]
83+ do
84+ echo "Waiting till aux instance of MySQL starts"
85+ sleep 1
86+ mysql_start_timeout=$(($mysql_start_timeout - 1))
87+ if [ $mysql_start_timeout -eq 0 ]; then echo "Can't start aux instance of MySQL. Exiting..."; exit ; fi
88+ done
89+}
90+
91+function shutdown_temp_mysql {
92+ echo "Shutting down temp MySQL..."
93+ mysqladmin --no-defaults -S $mysql_tmp_socket shutdown
94+}
95+
96+function on_error {
97+ if [ $? -ne 0 ]
98+ then
99+ echo ""
100+ echo "Error occured!"
101+ tail $work_dir/make.log
102+ #tail $mysql_temp_datadir/error.log
103+ #rm -vr $work_dir; rm -vr $mysql_temp_datadir;
104+ echo "Exiting..."
105+ #shutdown_temp_mysql
106+ #rm -vr $mysql_temp_datadir;
107+ fi
108+ shutdown_temp_mysql
109+ rm -vr $mysql_temp_datadir;
110+}
111+
112+function init() {
113+ # Check that the script is run from source directory
114+ if ! test -f "$top_dir/stream_parser.c"
115+ then
116+ echo "Script $0 must be run from a directory with TwinDB InnoDB Recovery Tool source code"
117+ exit 1
118+ fi
119+
120+ echo -n "Testing MySQL connection... "
121+ check_mysql_connection
122+ echo "OK"
123+
124+ echo -n "Initializing working directory... "
125+ if test -d "$work_dir"
126+ then
127+ echo "Directory $work_dir must not exist. Remove it and restart $0"
128+ exit 1
129+ fi
130+
131+ mkdir "$work_dir"
132+ echo "Workdir=$work_dir"
133+ cd "$work_dir"
134+ #trap "if [ $? -ne 0 ] ; then rm -r \"$work_dir\"; fi" EXIT
135+ echo "OK"
136+
137+ echo -n "Creating recovery database... "
138+ $MYSQL -e "CREATE DATABASE IF NOT EXISTS ${mysql_db}_recovered"
139+ $MYSQL_TMP -e "CREATE DATABASE IF NOT EXISTS ${mysql_db}_recovered"
140+
141+ echo -n "Building InnoDB parsers... "
142+ cd "$top_dir"
143+ make > "$work_dir/make.log" 2>&1
144+ cd "$work_dir"
145+ echo "OK"
146+
147+ # Get datadir
148+ echo "mdd=$mdd"
149+ if [ "$mdd" == "" ]
150+ then
151+ echo "Checking running mysql server for datadir..."
152+ datadir="`$MYSQL -e "SHOW VARIABLES LIKE 'datadir'" -NB | awk '{ $1 = ""; print $0}'| sed 's/^ //'`"
153+ innodb_file_per_table=`$MYSQL -e "SHOW VARIABLES LIKE 'innodb_file_per_table'" -NB | awk '{ print $2}'`
154+ innodb_data_file_path=`$MYSQL -e "SHOW VARIABLES LIKE 'innodb_data_file_path'" -NB | awk '{ $1 = ""; print $0}'| sed 's/^ //'`
155+ else
156+ echo "Using $mdd as a datadir..."
157+ datadir=$mdd
158+ #innodb_file_per_table="ON"
159+ #innodb_data_file_path=''
160+ fi
161+ if ! test -d "$datadir"
162+ then
163+ echo "$datadir does not exists! exiting..."
164+ exit 1
165+ else
166+ echo "Using datadir = $datadir"
167+ echo "InnoDB params: $innodb_file_per_table, $innodb_data_file_path"
168+ fi
169+
170+}
171+
172+function get_row_format() {
173+ # get row format
174+ Row_format=`$MYSQL -NB -e "SHOW TABLE STATUS LIKE '$t'" ${mysql_db}| awk '{print $4}'`
175+ is_56=`$MYSQL -NB -e "select @@version"| grep 5\.6`
176+ if [ "$Row_format" == "Compact" ]
177+ then
178+ Row_format_arg="-5"
179+ if ! test -z "$is_56"; then Row_format_arg="-6"; fi
180+ else
181+ Row_format_arg="-4"
182+ fi
183+ echo "... row format arg: $Row_format_arg"
184+}
185+
186+function recover_innodb_dict() {
187+ echo "Using datadir = $datadir"
188+ echo "Splitting InnoDB tablespace into pages... "
189+ old_IFS="$IFS"
190+ IFS=";"
191+ for ibdata in $innodb_data_file_path
192+ do
193+ ibdata_file=`echo $ibdata| awk -F: '{print $1}'`
194+ "$top_dir"/stream_parser -f "$datadir/$ibdata_file"
195+ done
196+ IFS=$old_IFS
197+ if [ $innodb_file_per_table == "ON" ] || [ $innodb_file_per_table == "1" ]
198+ then
199+ if [ "$tables" == "" ]
200+ then
201+ tables=`$MYSQL -NB -e "SELECT TABLE_NAME FROM TABLES WHERE TABLE_SCHEMA='$mysql_db' and ENGINE='InnoDB'" information_schema`
202+ fi
203+ echo "--- Recovering tables $tables in $mysql_db"
204+ for t in $tables
205+ do
206+ "$top_dir"/stream_parser -f "$datadir/$mysql_db/$t.ibd"
207+ done
208+ fi
209+ echo "OK"
210+
211+ echo -n "Actual recovering of InnoDB dictionary... "
212+ old_IFS="$IFS"
213+ IFS=";"
214+ for ibdata in $innodb_data_file_path
215+ do
216+ ibdata_file=`echo $ibdata| awk -F: '{print $1}'`
217+ dir="pages-$ibdata_file"/FIL_PAGE_INDEX/`printf "%016d" 1`.page
218+ mkdir -p "dumps/${mysql_db}_recovered"
219+ if test -f "$dir"
220+ then
221+ "$top_dir"/c_parser -4Uf "$dir" -p "${mysql_db}_recovered" \
222+ -t "$top_dir"/dictionary/SYS_TABLES.sql \
223+ >> "dumps/${mysql_db}_recovered/SYS_TABLES" \
224+ 2>SYS_TABLES.sql
225+ fi
226+ dir="pages-$ibdata_file"/FIL_PAGE_INDEX/`printf "%016d" 3`.page
227+ if test -f "$dir"
228+ then
229+ "$top_dir"/c_parser -4Uf "$dir" -p "${mysql_db}_recovered" \
230+ -t "$top_dir"/dictionary/SYS_INDEXES.sql \
231+ >> "dumps/${mysql_db}_recovered/SYS_INDEXES" \
232+ 2>SYS_INDEXES.sql
233+ fi
234+ done
235+ IFS=$old_IFS
236+
237+ $MYSQL_TMP $mydebug -e "DROP TABLE IF EXISTS SYS_TABLES" ${mysql_db}_recovered
238+ $MYSQL_TMP $mydebug -e "DROP TABLE IF EXISTS SYS_INDEXES" ${mysql_db}_recovered
239+ # load structure
240+ $MYSQL_TMP $mydebug ${mysql_db}_recovered < "$top_dir"/dictionary/SYS_TABLES.sql
241+ $MYSQL_TMP $mydebug ${mysql_db}_recovered < "$top_dir"/dictionary/SYS_INDEXES.sql
242+ # load data
243+ $MYSQL_TMP $mydebug ${mysql_db}_recovered < SYS_INDEXES.sql
244+ $MYSQL_TMP $mydebug ${mysql_db}_recovered < SYS_TABLES.sql
245+ echo "OK"
246+}
247+
248+function recover_tables() {
249+ echo -n "Recovering tables... "
250+ for t in $tables
251+ do
252+ # Create table structure
253+ #echo "pages-$t.ibd/$t.sql"
254+ if [ $USEFRM -eq 0 ]
255+ then
256+ mysqldump --skip-triggers --skip-add-drop-table --no-data ${mysql_db} $t > pages-$t.ibd/$t.sql
257+ else
258+ echo "Running mysqlfrm --diagnostic '$datadir/$mysql_db/$t.frm' ... "
259+ $MYSQL_TMP -e "create database if not exists ${mysql_db}"
260+ mysqlfrm -v --diagnostic $datadir/$mysql_db/$t.frm | egrep -v '^#' | $MYSQL_TMP # > pages-$t.ibd/$t.sql
261+ mysqldump -S $mysql_tmp_socket --skip-triggers --skip-add-drop-table --no-data ${mysql_db} $t > pages-$t.ibd/$t.sql
262+ fi
263+ if [ "$i" == "" ]
264+ then
265+ get_row_format
266+ else
267+ Row_format_arg="-5"
268+ fi
269+ # Get index id
270+ index_id=`$MYSQL_TMP -NB -e "SELECT SYS_INDEXES.ID FROM SYS_INDEXES JOIN SYS_TABLES ON SYS_INDEXES.TABLE_ID = SYS_TABLES.ID WHERE SYS_TABLES.NAME= '${mysql_db}/$t' ORDER BY ID LIMIT 1" ${mysql_db}_recovered`
271+ echo "index_id=$index_id"
272+ if [ $innodb_file_per_table == "ON" ] || [ $innodb_file_per_table == "1" ]
273+ then
274+ #"$top_dir"/c_parser $Row_format_arg -f "$datadir/$mysql_db/$t.ibd" \
275+ set -x
276+ "$top_dir"/c_parser $Row_format_arg -f "pages-$t.ibd/FIL_PAGE_INDEX/`printf '%016u' $index_id`.page" \
277+ -p "${mysql_db}_recovered" \
278+ -t "pages-$t.ibd/$t.sql" $UNDEL \
279+ > "dumps/${mysql_db}_recovered/$t" 2> $t.sql
280+ set +x
281+ else
282+ old_IFS="$IFS"
283+ IFS=";"
284+ for ibdata in $innodb_data_file_path
285+ do
286+ ibdata_file=`echo $ibdata| awk -F: '{print $1}'`
287+ dir="pages-$ibdata_file"/FIL_PAGE_INDEX/0-$index_id
288+ if test -d "$dir"
289+ then
290+ "$top_dir"/c_parser $Row_format_arg -f "$dir" -p "${mysql_db}_recovered" -b "pages-$ibdata_file/FIL_PAGE_TYPE_BLOB" -t "pages-$t.ibd/$t.sql" $UNDEL >> "dumps/${mysql_db}_r
291+ ecovered/$t" 2> $t.sql
292+ fi
293+
294+ done
295+ IFS="$old_IFS"
296+ fi
297+ done
298+ echo "OK"
299+}
300+
301+function load_to_mysql() {
302+ echo -n "Loading recovered data into MySQL... "
303+ for t in $tables
304+ do
305+ $MYSQL $mydebug -e "DROP TABLE IF EXISTS \`$t\`;CREATE TABLE $t like ${mysql_db}.$t" ${mysql_db}_recovered
306+ $MYSQL -vvv ${mysql_db}_recovered < $t.sql > $t.load.log 2>&1
307+ grep -A 4 'LOAD DATA' $t.load.log
308+ done
309+ echo "OK"
310+}
311+### Main
312+
313+if [ $# -lt 2 ] ; then
314+ echo 'Too few arguments supplied'
315+ usage
316+ exit 1
317+fi
318+
319+while getopts ":v:i:d:t:D:f" opt; do
320+ case $opt in
321+ v)
322+ VERBOSE=1
323+ ;;
324+ i)
325+ mdd=$OPTARG
326+ ;;
327+ d)
328+ mysql_db=$OPTARG
329+ if [ $VERBOSE -eq 1 ] ; then
330+ echo "Database: $mysql_db"
331+ fi
332+ ;;
333+ t)
334+ tables=$OPTARG
335+ if [ $VERBOSE -eq 1 ] ; then
336+ echo "TABLES: $tables"
337+ fi
338+ ;;
339+ D)
340+ UNDEL=" -D "
341+ echo "!!!UNDEL flag is set!!!"
342+ if [ $VERBOSE -eq 1 ] ; then
343+ echo "UNDEL flag is set"
344+ fi
345+ ;;
346+ f)
347+ USEFRM=1
348+ if [ $VERBOSE -eq 1 ] ; then
349+ echo "Will use FRMREADER utility to parse FRM files instead of mysqldump. (EXPERIMENTAL)"
350+ fi
351+ ;;
352+ esac
353+done
354+
355+echo "$v $mdd $mysql_db $tables"
356+
357+#check_mysql_connection
358+
359+trap on_error QUIT EXIT INT TERM ERR
360+
361+start_temp_mysql
362+echo $mysql_tmp_socket
363+MYSQL_TMP="mysql --no-defaults -S $mysql_tmp_socket "
364+if [ $VERBOSE -eq 1 ] ; then
365+ mydebug=" -vvv "
366+fi
367+
368+init
369+
370+#echo "Starting recovering InnoDB system dictionary..."
371+recover_innodb_dict
372+
373+recover_tables
374+load_to_mysql
375+cd "$top_dir"
376+
377+echo "Temp data is saved to $work_dir"
378+echo "All DONE."

Subscribers

People subscribed via source and target branches