~percona-dev/percona-server/5.1.56

« back to all changes in this revision

Viewing changes to innodb_expand_fast_index_creation.patch

  • Committer: Alexey Kopytov
  • Date: 2011-03-31 11:16:54 UTC
  • Revision ID: akopytov@gmail.com-20110331111654-aok07pwra3j0qh88
Bug #744103: Make use of InnoDB fast index creation in mysqldump, ALTER
TABLE and OPTIMIZE TABLE

Initial implementation of innodb_expand_fast_index_creation.patch.

This patch expands the applicability of InnoDB fast index creation to
mysqldump, ALTER TABLE and OPTIMIZE TABLE as follows:

1. mysqldump has now a new option, --innodb-optimize-keys, which changes
the way InnoDB tables are dumped so that secondary and foreign keys are
created after loading the data thus taking advantage of fast index
creation.

This part of the patch is an implementation of the feature request
reported as MySQL bug #49120.

More specifically:

- KEY, UNIQUE KEY and CONSTRAINT specifications are omitted from CREATE
TABLE corresponding to InnoDB tables.

- an additional ALTER TABLE is issued after dumping the data to create
the previously omitted keys.

Delaying foreign key creation does not introduce any additional risks as
mysqldump always prepends its output with SET FOREIGN_KEY_CHECKS=0 anyway.

2. When ALTER TABLE requires a table copy, secondary keys are now dropped
and recreated later after copying the data. The following restrictions
apply:

- only non-unique keys can be involved in this optimization

- if the table contains foreign keys, or a foreign key is being added as
a part of the current ALTER TABLE statement, the optimization is
disabled for all keys.

This part of the patch is an implementation of the feature request
reported as MySQL bug #57583.

3. As OPTIMIZE TABLE is mapped to ALTER TABLE ... ENGINE=InnoDB for
InnoDB tables, it now also benefits from fast index creation with the
same restrictions as for ALTER TABLE.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# name       : innodb_expand_fast_index_creation.patch
 
2
# maintainer : Alexey
 
3
#
 
4
# Expands the applicability of InnoDB fast index creation to mysqldump,
 
5
# ALTER TABLE and OPTIMIZE TABLE.
 
6
#
 
7
--- a/client/client_priv.h      2011-03-31 12:04:39.000000000 +0400
 
8
+++ b/client/client_priv.h      2011-03-31 12:10:21.000000000 +0400
 
9
@@ -96,5 +96,6 @@
 
10
   OPT_FIRST_SLAVE,
 
11
   OPT_ALL,
 
12
   OPT_NO_REMOVE_EOL_CARRET,
 
13
+  OPT_INNODB_OPTIMIZE_KEYS,
 
14
   OPT_MAX_CLIENT_OPTION
 
15
 };
 
16
--- a/client/mysqldump.c        2011-03-31 12:04:37.000000000 +0400
 
17
+++ b/client/mysqldump.c        2011-03-31 12:05:39.000000000 +0400
 
18
@@ -45,6 +45,7 @@
 
19
 #include <m_ctype.h>
 
20
 #include <hash.h>
 
21
 #include <stdarg.h>
 
22
+#include <my_list.h>
 
23
 
 
24
 #include "client_priv.h"
 
25
 #include "mysql.h"
 
26
@@ -134,6 +135,8 @@
 
27
 
 
28
 static my_bool server_supports_sql_no_fcache= FALSE;
 
29
 
 
30
+static my_bool opt_innodb_optimize_keys= FALSE;
 
31
+
 
32
 /*
 
33
 Dynamic_string wrapper functions. In this file use these
 
34
 wrappers, they will terminate the process if there is
 
35
@@ -179,6 +182,8 @@
 
36
 
 
37
 HASH ignore_table;
 
38
 
 
39
+LIST *skipped_keys_list;
 
40
+
 
41
 static struct my_option my_long_options[] =
 
42
 {
 
43
   {"all", OPT_ALL, "Deprecated. Use --create-options instead.",
 
44
@@ -325,6 +330,11 @@
 
45
    "be specified with both database and table names, e.g., "
 
46
    "--ignore-table=database.table.",
 
47
    0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
 
48
+  {"innodb-optimize-keys", OPT_INNODB_OPTIMIZE_KEYS,
 
49
+   "Use InnoDB fast index creation by creating secondary indexes after "
 
50
+   "dumping the data.",
 
51
+   &opt_innodb_optimize_keys, &opt_innodb_optimize_keys, 0, GET_BOOL, NO_ARG,
 
52
+   0, 0, 0, 0, 0, 0},
 
53
   {"insert-ignore", OPT_INSERT_IGNORE, "Insert rows with INSERT IGNORE.",
 
54
    &opt_ignore, &opt_ignore, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
 
55
    0, 0},
 
56
@@ -2240,6 +2250,77 @@
 
57
 }
 
58
 
 
59
 /*
 
60
+  Remove secondary/foreign key definitions from a given SHOW CREATE TABLE string
 
61
+  and store them into a temporary list to be used later.
 
62
+
 
63
+  SYNOPSIS
 
64
+    skip_secondary_keys()
 
65
+    create_str                SHOW CREATE TABLE output
 
66
+
 
67
+
 
68
+  DESCRIPTION
 
69
+
 
70
+    Stores all lines starting with "KEY" or "UNIQUE KEY" or "CONSTRAINT"
 
71
+    into skipped_keys_list and removes them from the input string.
 
72
+    Ignoring FOREIGN KEYS constraints when creating the table is ok, because
 
73
+    mysqldump sets foreign_key_checks to 0 anyway.
 
74
+*/
 
75
+
 
76
+static void skip_secondary_keys(char *create_str)
 
77
+{
 
78
+  char *ptr, *strend;
 
79
+  char *last_comma = NULL;
 
80
+
 
81
+  strend= create_str + strlen(create_str);
 
82
+
 
83
+  ptr= create_str;
 
84
+  while (*ptr)
 
85
+  {
 
86
+    char *tmp, *orig_ptr;
 
87
+
 
88
+    orig_ptr= ptr;
 
89
+    /* Skip leading whitespace */
 
90
+    while (*ptr && my_isspace(charset_info, *ptr))
 
91
+      ptr++;
 
92
+
 
93
+    /* Read the next line */
 
94
+    for (tmp= ptr; *tmp != '\n' && *tmp != '\0'; tmp++);
 
95
+
 
96
+    /* Is it a secondary index definition? */
 
97
+    if (*tmp == '\n' &&
 
98
+        (!strncmp(ptr, "UNIQUE KEY ", sizeof("UNIQUE KEY ") - 1) ||
 
99
+         !strncmp(ptr, "KEY ", sizeof("KEY ") - 1) ||
 
100
+         !strncmp(ptr, "CONSTRAINT ", sizeof("CONSTRAINT ") - 1)))
 
101
+    {
 
102
+      char *data, *end= tmp - 1;
 
103
+
 
104
+      /* Remove the trailing comma */
 
105
+      if (*end == ',')
 
106
+        end--;
 
107
+      data= my_strndup(ptr, end - ptr + 1, MYF(MY_FAE));
 
108
+      skipped_keys_list= list_cons(data, skipped_keys_list);
 
109
+
 
110
+      memmove(orig_ptr, tmp + 1, strend - tmp);
 
111
+      ptr= orig_ptr;
 
112
+      strend-= tmp + 1 - ptr;
 
113
+
 
114
+      /* Remove the comma on the previos line */
 
115
+      if (last_comma != NULL)
 
116
+      {
 
117
+        *last_comma= ' ';
 
118
+        last_comma = NULL;
 
119
+      }
 
120
+    }
 
121
+    else
 
122
+    {
 
123
+      if (tmp[-1] == ',')
 
124
+        last_comma= tmp - 1;
 
125
+      ptr= (*tmp == '\0') ? tmp : tmp + 1;
 
126
+    }
 
127
+  }
 
128
+}
 
129
+
 
130
+/*
 
131
   get_table_structure -- retrievs database structure, prints out corresponding
 
132
   CREATE statement and fills out insert_pat if the table is the type we will
 
133
   be dumping.
 
134
@@ -2480,6 +2561,9 @@
 
135
 
 
136
       row= mysql_fetch_row(result);
 
137
 
 
138
+      if (opt_innodb_optimize_keys && !strcmp(table_type, "InnoDB"))
 
139
+        skip_secondary_keys(row[1]);
 
140
+
 
141
       fprintf(sql_file, (opt_compatible_mode & 3) ? "%s;\n" :
 
142
               "/*!40101 SET @saved_cs_client     = @@character_set_client */;\n"
 
143
               "/*!40101 SET character_set_client = utf8 */;\n"
 
144
@@ -3572,6 +3656,27 @@
 
145
       goto err;
 
146
     }
 
147
 
 
148
+    /* Perform delayed secondary index creation for --innodb-optimize-keys */
 
149
+    if (skipped_keys_list)
 
150
+    {
 
151
+      uint keys;
 
152
+      skipped_keys_list= list_reverse(skipped_keys_list);
 
153
+      fprintf(md_result_file, "ALTER TABLE %s ", opt_quoted_table);
 
154
+      for (keys= list_length(skipped_keys_list); keys > 0; keys--)
 
155
+      {
 
156
+        LIST *node= skipped_keys_list;
 
157
+        char *def= node->data;
 
158
+
 
159
+        fprintf(md_result_file, "ADD %s%s", def, (keys > 1) ? ", " : ";\n");
 
160
+
 
161
+        skipped_keys_list= list_delete(skipped_keys_list, node);
 
162
+        my_free(def, MYF(0));
 
163
+        my_free(node, MYF(0));
 
164
+      }
 
165
+
 
166
+      DBUG_ASSERT(skipped_keys_list == NULL);
 
167
+    }
 
168
+
 
169
     /* Moved enable keys to before unlock per bug 15977 */
 
170
     if (opt_disable_keys)
 
171
     {
 
172
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
 
173
+++ b/mysql-test/r/percona_mysqldump_innodb_optimize_keys.result        2011-03-31 12:05:39.000000000 +0400
 
174
@@ -0,0 +1,109 @@
 
175
+#
 
176
+# Test the --innodb-optimize-keys option.
 
177
+#
 
178
+CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT, KEY(b)) ENGINE=MyISAM;
 
179
+######################################
 
180
+
 
181
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 
182
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 
183
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 
184
+/*!40101 SET NAMES utf8 */;
 
185
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
 
186
+/*!40103 SET TIME_ZONE='+00:00' */;
 
187
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
 
188
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
 
189
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
 
190
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
 
191
+DROP TABLE IF EXISTS `t1`;
 
192
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
 
193
+/*!40101 SET character_set_client = utf8 */;
 
194
+CREATE TABLE `t1` (
 
195
+  `a` int(11) NOT NULL,
 
196
+  `b` int(11) DEFAULT NULL,
 
197
+  PRIMARY KEY (`a`),
 
198
+  KEY `b` (`b`)
 
199
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
 
200
+/*!40101 SET character_set_client = @saved_cs_client */;
 
201
+
 
202
+LOCK TABLES `t1` WRITE;
 
203
+/*!40000 ALTER TABLE `t1` DISABLE KEYS */;
 
204
+/*!40000 ALTER TABLE `t1` ENABLE KEYS */;
 
205
+UNLOCK TABLES;
 
206
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
207
+
 
208
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 
209
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
 
210
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
 
211
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 
212
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 
213
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 
214
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
 
215
+
 
216
+######################################
 
217
+DROP TABLE t1;
 
218
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB;
 
219
+INSERT INTO t2 VALUES (0), (1), (2);
 
220
+CREATE TABLE t1 (
 
221
+id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
 
222
+a INT, b VARCHAR(255), c DECIMAL(10,3),
 
223
+KEY (b),
 
224
+UNIQUE KEY uniq(c,a),
 
225
+FOREIGN KEY (a) REFERENCES t2(a) ON DELETE CASCADE
 
226
+) ENGINE=InnoDB;
 
227
+INSERT INTO t1(a,b,c) VALUES (0, "0", 0.0), (1, "1", 1.1), (2, "2", 2.2);
 
228
+######################################
 
229
+
 
230
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 
231
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 
232
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 
233
+/*!40101 SET NAMES utf8 */;
 
234
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
 
235
+/*!40103 SET TIME_ZONE='+00:00' */;
 
236
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
 
237
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
 
238
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
 
239
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
 
240
+DROP TABLE IF EXISTS `t1`;
 
241
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
 
242
+/*!40101 SET character_set_client = utf8 */;
 
243
+CREATE TABLE `t1` (
 
244
+  `id` int(11) NOT NULL AUTO_INCREMENT,
 
245
+  `a` int(11) DEFAULT NULL,
 
246
+  `b` varchar(255) DEFAULT NULL,
 
247
+  `c` decimal(10,3) DEFAULT NULL,
 
248
+  PRIMARY KEY (`id`) 
 
249
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
 
250
+/*!40101 SET character_set_client = @saved_cs_client */;
 
251
+
 
252
+LOCK TABLES `t1` WRITE;
 
253
+/*!40000 ALTER TABLE `t1` DISABLE KEYS */;
 
254
+INSERT INTO `t1` VALUES (1,0,'0','0.000'),(2,1,'1','1.100'),(3,2,'2','2.200');
 
255
+ALTER TABLE `t1` ADD UNIQUE KEY `uniq` (`c`,`a`), ADD KEY `b` (`b`), ADD KEY `a` (`a`), ADD CONSTRAINT `t1_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t2` (`a`) ON DELETE CASCADE;
 
256
+/*!40000 ALTER TABLE `t1` ENABLE KEYS */;
 
257
+UNLOCK TABLES;
 
258
+DROP TABLE IF EXISTS `t2`;
 
259
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
 
260
+/*!40101 SET character_set_client = utf8 */;
 
261
+CREATE TABLE `t2` (
 
262
+  `a` int(11) NOT NULL,
 
263
+  PRIMARY KEY (`a`)
 
264
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
265
+/*!40101 SET character_set_client = @saved_cs_client */;
 
266
+
 
267
+LOCK TABLES `t2` WRITE;
 
268
+/*!40000 ALTER TABLE `t2` DISABLE KEYS */;
 
269
+INSERT INTO `t2` VALUES (0),(1),(2);
 
270
+/*!40000 ALTER TABLE `t2` ENABLE KEYS */;
 
271
+UNLOCK TABLES;
 
272
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
273
+
 
274
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 
275
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
 
276
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
 
277
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 
278
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 
279
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 
280
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
 
281
+
 
282
+######################################
 
283
+DROP TABLE t1, t2;
 
284
--- a/mysql-test/suite/innodb_plugin/r/innodb.result    2011-03-31 12:04:33.000000000 +0400
 
285
+++ b/mysql-test/suite/innodb_plugin/r/innodb.result    2011-03-31 12:05:39.000000000 +0400
 
286
@@ -1679,7 +1679,7 @@
 
287
 71
 
288
 SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted';
 
289
 variable_value - @innodb_rows_inserted_orig
 
290
-1067
 
291
+1109
 
292
 SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated';
 
293
 variable_value - @innodb_rows_updated_orig
 
294
 866
 
295
--- a/mysql-test/suite/innodb_plugin/t/innodb-index.test        2011-02-11 22:49:34.000000000 +0300
 
296
+++ b/mysql-test/suite/innodb_plugin/t/innodb-index.test        2011-03-31 12:05:39.000000000 +0400
 
297
@@ -38,6 +38,11 @@
 
298
 show create table t1;
 
299
 --error ER_MULTIPLE_PRI_KEY
 
300
 alter table t1 add primary key (c);
 
301
+# Suppress the error log messages occuring on duplicate key error
 
302
+# during ALTER TABLE when using fast index creation
 
303
+--disable_query_log
 
304
+call mtr.add_suppression("Cannot find index PRIMARY in InnoDB index translation table.");
 
305
+--enable_query_log
 
306
 --error ER_DUP_ENTRY
 
307
 alter table t1 drop primary key, add primary key (b);
 
308
 create unique index c on t1 (c);
 
309
--- a/mysql-test/suite/innodb_plugin/t/innodb.test      2011-02-11 22:49:35.000000000 +0300
 
310
+++ b/mysql-test/suite/innodb_plugin/t/innodb.test      2011-03-31 12:05:39.000000000 +0400
 
311
@@ -15,6 +15,12 @@
 
312
 
 
313
 -- source include/have_innodb_plugin.inc
 
314
 
 
315
+# Suppress the error log message occuring on duplicate key error
 
316
+# during ALTER TABLE when using fast index creation
 
317
+--disable_query_log
 
318
+call mtr.add_suppression("Cannot find index v_2 in InnoDB index translation table.");
 
319
+--enable_query_log
 
320
+
 
321
 let $MYSQLD_DATADIR= `select @@datadir`;
 
322
 
 
323
 # Save the original values of some variables in order to be able to
 
324
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
 
325
+++ b/mysql-test/t/percona_mysqldump_innodb_optimize_keys.test  2011-03-31 12:05:39.000000000 +0400
 
326
@@ -0,0 +1,62 @@
 
327
+# Embedded server doesn't support external clients
 
328
+--source include/not_embedded.inc
 
329
+
 
330
+# Fast index creation is only available in InnoDB plugin
 
331
+--source include/have_innodb_plugin.inc
 
332
+
 
333
+# Save the initial number of concurrent sessions
 
334
+--source include/count_sessions.inc
 
335
+
 
336
+--echo #
 
337
+--echo # Test the --innodb-optimize-keys option.
 
338
+--echo #
 
339
+
 
340
+--let $file=$MYSQLTEST_VARDIR/tmp/t1.sql
 
341
+
 
342
+# First test that the option has no effect on non-InnoDB tables
 
343
+
 
344
+CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT, KEY(b)) ENGINE=MyISAM;
 
345
+
 
346
+--exec $MYSQL_DUMP --skip-comments --innodb-optimize-keys test t1 >$file
 
347
+
 
348
+--echo ######################################
 
349
+--cat_file $file
 
350
+--echo ######################################
 
351
+
 
352
+--remove_file $file
 
353
+
 
354
+DROP TABLE t1;
 
355
+
 
356
+
 
357
+# Check that for InnoDB tables secondary and foreign keys are created
 
358
+# after the data is dumped
 
359
+
 
360
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB;
 
361
+INSERT INTO t2 VALUES (0), (1), (2);
 
362
+
 
363
+CREATE TABLE t1 (
 
364
+  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
 
365
+  a INT, b VARCHAR(255), c DECIMAL(10,3),
 
366
+  KEY (b),
 
367
+  UNIQUE KEY uniq(c,a),
 
368
+  FOREIGN KEY (a) REFERENCES t2(a) ON DELETE CASCADE
 
369
+) ENGINE=InnoDB;
 
370
+
 
371
+INSERT INTO t1(a,b,c) VALUES (0, "0", 0.0), (1, "1", 1.1), (2, "2", 2.2);
 
372
+
 
373
+--exec $MYSQL_DUMP --skip-comments --innodb-optimize-keys test t1 t2 >$file
 
374
+
 
375
+--echo ######################################
 
376
+--cat_file $file
 
377
+--echo ######################################
 
378
+
 
379
+# Check that the resulting dump can be imported back
 
380
+
 
381
+--exec $MYSQL test < $file
 
382
+
 
383
+--remove_file $file
 
384
+
 
385
+DROP TABLE t1, t2;
 
386
+
 
387
+# Wait till we reached the initial number of concurrent sessions
 
388
+--source include/wait_until_count_sessions.inc
 
389
--- a/sql/sql_lex.cc    2011-03-31 12:04:37.000000000 +0400
 
390
+++ b/sql/sql_lex.cc    2011-03-31 12:13:39.000000000 +0400
 
391
@@ -1494,6 +1494,9 @@
 
392
   alter_list(rhs.alter_list, mem_root),
 
393
   key_list(rhs.key_list, mem_root),
 
394
   create_list(rhs.create_list, mem_root),
 
395
+  delayed_key_list(rhs.delayed_key_list, mem_root),
 
396
+  delayed_key_info(rhs.delayed_key_info),
 
397
+  delayed_key_count(rhs.delayed_key_count),
 
398
   flags(rhs.flags),
 
399
   keys_onoff(rhs.keys_onoff),
 
400
   tablespace_op(rhs.tablespace_op),
 
401
@@ -1516,6 +1519,7 @@
 
402
   list_copy_and_replace_each_value(alter_list, mem_root);
 
403
   list_copy_and_replace_each_value(key_list, mem_root);
 
404
   list_copy_and_replace_each_value(create_list, mem_root);
 
405
+  list_copy_and_replace_each_value(delayed_key_list, mem_root);
 
406
   /* partition_names are not deeply copied currently */
 
407
 }
 
408
 
 
409
--- a/sql/sql_lex.h     2011-03-31 12:04:38.000000000 +0400
 
410
+++ b/sql/sql_lex.h     2011-03-31 12:14:36.000000000 +0400
 
411
@@ -896,6 +896,9 @@
 
412
   List<Alter_column>            alter_list;
 
413
   List<Key>                     key_list;
 
414
   List<Create_field>            create_list;
 
415
+  List<Key>                     delayed_key_list;
 
416
+  KEY                           *delayed_key_info;
 
417
+  uint                          delayed_key_count;
 
418
   uint                          flags;
 
419
   enum enum_enable_or_disable   keys_onoff;
 
420
   enum tablespace_op_type       tablespace_op;
 
421
@@ -907,6 +910,8 @@
 
422
 
 
423
 
 
424
   Alter_info() :
 
425
+    delayed_key_info(NULL),
 
426
+    delayed_key_count(0),
 
427
     flags(0),
 
428
     keys_onoff(LEAVE_AS_IS),
 
429
     tablespace_op(NO_TABLESPACE_OP),
 
430
@@ -922,6 +927,9 @@
 
431
     alter_list.empty();
 
432
     key_list.empty();
 
433
     create_list.empty();
 
434
+    delayed_key_list.empty();
 
435
+    delayed_key_info= NULL;
 
436
+    delayed_key_count= 0;
 
437
     flags= 0;
 
438
     keys_onoff= LEAVE_AS_IS;
 
439
     tablespace_op= NO_TABLESPACE_OP;
 
440
--- a/sql/sql_table.cc  2011-03-31 12:04:35.000000000 +0400
 
441
+++ b/sql/sql_table.cc  2011-03-31 12:05:39.000000000 +0400
 
442
@@ -2558,7 +2558,7 @@
 
443
       file                      The handler for the new table.
 
444
       key_info_buffer     OUT   An array of KEY structs for the indexes.
 
445
       key_count           OUT   The number of elements in the array.
 
446
-      select_field_count        The number of fields coming from a select table.
 
447
+      select_field_count        The number of fields coming from a select table. 
 
448
 
 
449
   DESCRIPTION
 
450
     Prepares the table and key structures for table creation.
 
451
@@ -2913,7 +2913,6 @@
 
452
   }
 
453
 
 
454
   /* Create keys */
 
455
-
 
456
   List_iterator<Key> key_iterator(alter_info->key_list);
 
457
   List_iterator<Key> key_iterator2(alter_info->key_list);
 
458
   uint key_parts=0, fk_key_count=0;
 
459
@@ -3013,6 +3012,14 @@
 
460
   if (!*key_info_buffer || ! key_part_info)
 
461
     DBUG_RETURN(TRUE);                         // Out of memory
 
462
 
 
463
+  List_iterator<Key> delayed_key_iterator(alter_info->delayed_key_list);
 
464
+  alter_info->delayed_key_count= 0;
 
465
+  if (alter_info->delayed_key_list.elements > 0)
 
466
+  {
 
467
+    alter_info->delayed_key_info= (KEY *) sql_calloc(sizeof(KEY) *
 
468
+                                                     (*key_count));
 
469
+  }
 
470
+
 
471
   key_iterator.rewind();
 
472
   key_number=0;
 
473
   for (; (key=key_iterator++) ; key_number++)
 
474
@@ -3382,8 +3389,26 @@
 
475
       my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length);
 
476
       DBUG_RETURN(TRUE);
 
477
     }
 
478
+
 
479
+    if (alter_info->delayed_key_list.elements > 0)
 
480
+    {
 
481
+      Key *delayed_key;
 
482
+
 
483
+      delayed_key_iterator.rewind();
 
484
+      while ((delayed_key= delayed_key_iterator++))
 
485
+      {
 
486
+        if (delayed_key == key)
 
487
+        {
 
488
+         alter_info->delayed_key_info[alter_info->delayed_key_count++]=
 
489
+           *key_info;
 
490
+         break;
 
491
+        }
 
492
+      }
 
493
+    }
 
494
+
 
495
     key_info++;
 
496
   }
 
497
+
 
498
   if (!unique_key && !primary_key &&
 
499
       (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY))
 
500
   {
 
501
@@ -6081,6 +6106,10 @@
 
502
   List<Create_field> new_create_list;
 
503
   /* New key definitions are added here */
 
504
   List<Key> new_key_list;
 
505
+  /* List with secondary keys which should be created after copying the data */
 
506
+  List<Key> delayed_key_list;
 
507
+  /* Foreign key list returned by handler::get_foreign_key_list() */
 
508
+  List<FOREIGN_KEY_INFO> f_key_list;
 
509
   List_iterator<Alter_drop> drop_it(alter_info->drop_list);
 
510
   List_iterator<Create_field> def_it(alter_info->create_list);
 
511
   List_iterator<Alter_column> alter_it(alter_info->alter_list);
 
512
@@ -6093,6 +6122,7 @@
 
513
   uint used_fields= create_info->used_fields;
 
514
   KEY *key_info=table->key_info;
 
515
   bool rc= TRUE;
 
516
+  bool skip_secondary;
 
517
 
 
518
   DBUG_ENTER("mysql_prepare_alter_table");
 
519
 
 
520
@@ -6120,6 +6150,7 @@
 
521
     char *tablespace= static_cast<char *>(thd->alloc(FN_LEN + 1));
 
522
     /*
 
523
        Regular alter table of disk stored table (no tablespace/storage change)
 
524
+
 
525
        Copy tablespace name
 
526
     */
 
527
     if (tablespace &&
 
528
@@ -6270,7 +6301,23 @@
 
529
   /*
 
530
     Collect all keys which isn't in drop list. Add only those
 
531
     for which some fields exists.
 
532
-  */
 
533
+
 
534
+    We also skip secondary keys for InnoDB tables and store them in
 
535
+    delayed_key_list to create them later after copying the data to make use of
 
536
+    the InnoDB fast index creation. The following conditions must be met:
 
537
+
 
538
+    - we are going to create an InnoDB table;
 
539
+    - the key most be a non-UNIQUE one;
 
540
+    - there are no foreign keys. This can be optimized later to exclude only
 
541
+      those keys which are a part of foreign key constraints. Currently we
 
542
+      simply disable this optimization for all keys if there are any foreign
 
543
+      key constraints in the table.
 
544
+  */
 
545
+
 
546
+  skip_secondary=
 
547
+    !my_strcasecmp(system_charset_info, table->file->table_type(), "InnoDB") &&
 
548
+    !table->file->get_foreign_key_list(thd, &f_key_list) &&
 
549
+    f_key_list.elements == 0;
 
550
 
 
551
   for (uint i=0 ; i < table->s->keys ; i++,key_info++)
 
552
   {
 
553
@@ -6373,6 +6420,8 @@
 
554
                    test(key_info->flags & HA_GENERATED_KEY),
 
555
                    key_parts);
 
556
       new_key_list.push_back(key);
 
557
+      if (skip_secondary && key_type == Key::MULTIPLE)
 
558
+        delayed_key_list.push_back(key);
 
559
     }
 
560
   }
 
561
   {
 
562
@@ -6380,7 +6429,21 @@
 
563
     while ((key=key_it++))                     // Add new keys
 
564
     {
 
565
       if (key->type != Key::FOREIGN_KEY)
 
566
-        new_key_list.push_back(key);
 
567
+      {
 
568
+          new_key_list.push_back(key);
 
569
+        if (skip_secondary && key->type == Key::MULTIPLE)
 
570
+          delayed_key_list.push_back(key);
 
571
+      }
 
572
+      else if (skip_secondary)
 
573
+      {
 
574
+        /*
 
575
+          We are adding a foreign key so disable the secondary keys
 
576
+          optimization.
 
577
+        */
 
578
+        skip_secondary= FALSE;
 
579
+        delayed_key_list.empty();
 
580
+      }
 
581
+
 
582
       if (key->name &&
 
583
          !my_strcasecmp(system_charset_info,key->name,primary_key_name))
 
584
       {
 
585
@@ -6429,12 +6492,100 @@
 
586
   rc= FALSE;
 
587
   alter_info->create_list.swap(new_create_list);
 
588
   alter_info->key_list.swap(new_key_list);
 
589
+  alter_info->delayed_key_list.swap(delayed_key_list);
 
590
 err:
 
591
   DBUG_RETURN(rc);
 
592
 }
 
593
 
 
594
 
 
595
 /*
 
596
+  Temporarily remove secondary keys previously stored in
 
597
+  alter_info->delayed_key_info.
 
598
+*/
 
599
+static int
 
600
+remove_secondary_keys(THD *thd, TABLE *table, Alter_info *alter_info)
 
601
+{
 
602
+  uint *key_numbers;
 
603
+  uint key_counter= 0;
 
604
+  uint i;
 
605
+  int error;
 
606
+  DBUG_ENTER("remove_secondary_keys");
 
607
+  DBUG_ASSERT(alter_info->delayed_key_count > 0);
 
608
+
 
609
+  key_numbers= (uint *) thd->alloc(sizeof(uint) *
 
610
+                                   alter_info->delayed_key_count);
 
611
+  for (i= 0; i < alter_info->delayed_key_count; i++)
 
612
+  {
 
613
+    KEY *key= alter_info->delayed_key_info + i;
 
614
+    uint j;
 
615
+
 
616
+    for (j= 0; j < table->s->keys; j++)
 
617
+    {
 
618
+      if (!strcmp(table->key_info[j].name, key->name))
 
619
+      {
 
620
+        key_numbers[key_counter++]= j;
 
621
+        break;
 
622
+      }
 
623
+    }
 
624
+  }
 
625
+
 
626
+  DBUG_ASSERT(key_counter == alter_info->delayed_key_count);
 
627
+
 
628
+  if ((error= table->file->prepare_drop_index(table, key_numbers,
 
629
+                                              key_counter)) ||
 
630
+      (error= table->file->final_drop_index(table)))
 
631
+  {
 
632
+    table->file->print_error(error, MYF(0));
 
633
+  }
 
634
+
 
635
+  DBUG_RETURN(error);
 
636
+}
 
637
+
 
638
+/*
 
639
+  Restore secondary keys previously removed in remove_secondary_keys.
 
640
+*/
 
641
+
 
642
+static int
 
643
+restore_secondary_keys(THD *thd, TABLE *table, Alter_info *alter_info)
 
644
+{
 
645
+  uint i;
 
646
+  int error;
 
647
+  DBUG_ENTER("restore_secondary_keys");
 
648
+  DBUG_ASSERT(alter_info->delayed_key_count > 0);
 
649
+
 
650
+  thd_proc_info(thd, "restoring secondary keys");
 
651
+
 
652
+  /* Fix the key parts */
 
653
+  for (i= 0; i < alter_info->delayed_key_count; i++)
 
654
+  {
 
655
+    KEY *key = alter_info->delayed_key_info + i;
 
656
+    KEY_PART_INFO *key_part;
 
657
+    KEY_PART_INFO *part_end;
 
658
+
 
659
+    part_end= key->key_part + key->key_parts;
 
660
+    for (key_part= key->key_part; key_part < part_end; key_part++)
 
661
+      key_part->field= table->field[key_part->fieldnr];
 
662
+  }
 
663
+
 
664
+  if ((error= table->file->add_index(table, alter_info->delayed_key_info,
 
665
+                                     alter_info->delayed_key_count)))
 
666
+  {
 
667
+    /*
 
668
+      Exchange the key_info for the error message. If we exchange
 
669
+      key number by key name in the message later, we need correct info.
 
670
+    */
 
671
+    KEY *save_key_info= table->key_info;
 
672
+    table->key_info= alter_info->delayed_key_info;
 
673
+    table->file->print_error(error, MYF(0));
 
674
+    table->key_info= save_key_info;
 
675
+
 
676
+    DBUG_RETURN(error);
 
677
+  }
 
678
+
 
679
+  DBUG_RETURN(0);
 
680
+}
 
681
+
 
682
+/*
 
683
   Alter table
 
684
 
 
685
   SYNOPSIS
 
686
@@ -7218,6 +7369,12 @@
 
687
   else
 
688
     create_info->data_file_name=create_info->index_file_name=0;
 
689
 
 
690
+  /*
 
691
+    Postpone secondary index creation for InnoDB tables if the data has to be
 
692
+    copied.
 
693
+    TODO: is there a better way to check for InnoDB?
 
694
+  */
 
695
+
 
696
   DEBUG_SYNC(thd, "alter_table_before_create_table_no_lock");
 
697
   /*
 
698
     Create a table with a temporary name.
 
699
@@ -7277,12 +7434,21 @@
 
700
     /* We don't want update TIMESTAMP fields during ALTER TABLE. */
 
701
     new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
 
702
     new_table->next_number_field=new_table->found_next_number_field;
 
703
+
 
704
+    if (alter_info->delayed_key_count > 0)
 
705
+    {
 
706
+      /* ignore the error */
 
707
+      error= remove_secondary_keys(thd, new_table, alter_info);
 
708
+    }
 
709
+
 
710
     thd_proc_info(thd, "copy to tmp table");
 
711
     error= copy_data_between_tables(table, new_table,
 
712
                                     alter_info->create_list, ignore,
 
713
                                     order_num, order, &copied, &deleted,
 
714
                                     alter_info->keys_onoff,
 
715
                                     alter_info->error_if_not_empty);
 
716
+    if (!error && alter_info->delayed_key_count > 0)
 
717
+      error= restore_secondary_keys(thd, new_table, alter_info);
 
718
   }
 
719
   else
 
720
   {