summaryrefslogtreecommitdiff
path: root/storage/innobase/dict/drop.cc
blob: 35b732c53b6c09fedfc8343bff42cc584a3f5b7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/*****************************************************************************

Copyright (c) 2021, 2022, MariaDB Corporation.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA

*****************************************************************************/

/**
@file dict/drop.cc
Data Dictionary Language operations that delete .ibd files */

/* We implement atomic data dictionary operations as follows.

1. A data dictionary transaction is started.
2. We acquire exclusive lock on all the tables that are to be dropped
during the execution of the transaction.
3. We lock the data dictionary cache.
4. All metadata tables will be updated within the single DDL transaction,
including deleting or renaming InnoDB persistent statistics.
4b. If any lock wait would occur while we are holding the dict_sys latches,
we will instantly report a timeout error and roll back the transaction.
5. The transaction metadata is marked as committed.
6. If any files were deleted, we will durably write FILE_DELETE
to the redo log and start deleting the files.
6b. Also purge after a commit may perform file deletion. This is also the
recovery mechanism if the server was killed between step 5 and 6.
7. We unlock the data dictionary cache.
8. The file handles of the unlinked files will be closed. This will actually
reclaim the space in the file system (delete-on-close semantics).

Notes:

(a) Purge will be locked out by MDL. For internal tables related to
FULLTEXT INDEX, purge will not acquire MDL on the user table name,
and therefore, when we are dropping any FTS_ tables, we must suspend
and resume purge to prevent a race condition.

(b) If a transaction needs to both drop and create a table by some
name, it must rename the table in between. This is used by
ha_innobase::truncate() and fts_drop_common_tables().

(c) No data is ever destroyed before the transaction is committed,
so we can trivially roll back the transaction at any time.
Lock waits during a DDL operation are no longer a fatal error
that would cause the InnoDB to hang or to intentionally crash.
(Only ALTER TABLE...DISCARD TABLESPACE may discard data before commit.)

(d) The only changes to the data dictionary cache that are performed
before transaction commit and must be rolled back explicitly are as follows:
(d1) fts_optimize_add_table() to undo fts_optimize_remove_table()
*/

#include "trx0purge.h"
#include "dict0dict.h"
#include "dict0stats.h"
#include "dict0stats_bg.h"

#include "dict0defrag_bg.h"
#include "btr0defragment.h"
#include "ibuf0ibuf.h"
#include "lock0lock.h"

#include "que0que.h"
#include "pars0pars.h"

/** Try to drop the foreign key constraints for a persistent table.
@param name        name of persistent table
@return error code */
dberr_t trx_t::drop_table_foreign(const table_name_t &name)
{
  ut_ad(dict_sys.locked());
  ut_ad(state == TRX_STATE_ACTIVE);
  ut_ad(dict_operation);
  ut_ad(dict_operation_lock_mode);

  if (!dict_sys.sys_foreign || dict_sys.sys_foreign->corrupted)
    return DB_SUCCESS;

  if (!dict_sys.sys_foreign_cols || dict_sys.sys_foreign_cols->corrupted)
    return DB_SUCCESS;

  pars_info_t *info= pars_info_create();
  pars_info_add_str_literal(info, "name", name.m_name);
  return que_eval_sql(info,
                      "PROCEDURE DROP_FOREIGN() IS\n"
                      "fid CHAR;\n"

                      "DECLARE CURSOR fk IS\n"
                      "SELECT ID FROM SYS_FOREIGN\n"
                      "WHERE FOR_NAME=:name\n"
                      "AND TO_BINARY(FOR_NAME)=TO_BINARY(:name)\n"
                      "FOR UPDATE;\n"

                      "BEGIN\n"
                      "OPEN fk;\n"
                      "WHILE 1=1 LOOP\n"
                      "  FETCH fk INTO fid;\n"
                      "  IF (SQL % NOTFOUND)THEN RETURN;END IF;\n"
                      "  DELETE FROM SYS_FOREIGN_COLS"
                      " WHERE ID=fid;\n"
                      "  DELETE FROM SYS_FOREIGN WHERE ID=fid;\n"
                      "END LOOP;\n"
                      "CLOSE fk;\n"
                      "END;\n", this);
}

/** Try to drop the statistics for a persistent table.
@param name        name of persistent table
@return error code */
dberr_t trx_t::drop_table_statistics(const table_name_t &name)
{
  ut_ad(dict_sys.locked());
  ut_ad(dict_operation_lock_mode);

  if (strstr(name.m_name, "/" TEMP_FILE_PREFIX_INNODB) ||
      !strcmp(name.m_name, TABLE_STATS_NAME) ||
      !strcmp(name.m_name, INDEX_STATS_NAME))
    return DB_SUCCESS;

  char db[MAX_DB_UTF8_LEN], table[MAX_TABLE_UTF8_LEN];
  dict_fs2utf8(name.m_name, db, sizeof db, table, sizeof table);

  dberr_t err= dict_stats_delete_from_table_stats(db, table, this);
  if (err == DB_SUCCESS || err == DB_STATS_DO_NOT_EXIST)
  {
    err= dict_stats_delete_from_index_stats(db, table, this);
    if (err == DB_STATS_DO_NOT_EXIST)
      err= DB_SUCCESS;
  }
  return err;
}

/** Try to drop a persistent table.
@param table       persistent table
@param fk          whether to drop FOREIGN KEY metadata
@return error code */
dberr_t trx_t::drop_table(const dict_table_t &table)
{
  ut_ad(dict_sys.locked());
  ut_ad(state == TRX_STATE_ACTIVE);
  ut_ad(dict_operation);
  ut_ad(dict_operation_lock_mode);
  ut_ad(!table.is_temporary());
  /* The table must be exclusively locked by this transaction. */
  ut_ad(table.get_ref_count() <= 1);
  ut_ad(table.n_lock_x_or_s == 1);
  ut_ad(UT_LIST_GET_LEN(table.locks) >= 1);
#ifdef UNIV_DEBUG
  bool found_x= false;
  for (lock_t *lock= UT_LIST_GET_FIRST(table.locks); lock;
       lock= UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock))
  {
    ut_ad(lock->trx == this);
    switch (lock->type_mode) {
    case LOCK_TABLE | LOCK_X:
      found_x= true;
      break;
    case LOCK_TABLE | LOCK_IX:
    case LOCK_TABLE | LOCK_AUTO_INC:
      break;
    default:
      ut_ad("unexpected lock type" == 0);
    }
  }
  ut_ad(found_x);
#endif

  if (dict_sys.sys_virtual && !dict_sys.sys_virtual->corrupted)
  {
    pars_info_t *info= pars_info_create();
    pars_info_add_ull_literal(info, "id", table.id);
    if (dberr_t err= que_eval_sql(info,
                                  "PROCEDURE DROP_VIRTUAL() IS\n"
                                  "BEGIN\n"
                                  "DELETE FROM SYS_VIRTUAL"
                                  " WHERE TABLE_ID=:id;\n"
                                  "END;\n", this))
      return err;
  }

  /* Once DELETE FROM SYS_INDEXES is committed, purge may invoke
  dict_drop_index_tree(). */

  if (!(table.flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)));
  else if (dberr_t err= fts_drop_tables(this, table))
  {
    ib::error() << "Unable to remove FTS tables for "
                << table.name << ": " << err;
    return err;
  }

  mod_tables.emplace(const_cast<dict_table_t*>(&table), undo_no).
    first->second.set_dropped();

  pars_info_t *info= pars_info_create();
  pars_info_add_ull_literal(info, "id", table.id);
  return que_eval_sql(info,
                      "PROCEDURE DROP_TABLE() IS\n"
                      "iid CHAR;\n"

                      "DECLARE CURSOR idx IS\n"
                      "SELECT ID FROM SYS_INDEXES\n"
                      "WHERE TABLE_ID=:id FOR UPDATE;\n"

                      "BEGIN\n"

                      "DELETE FROM SYS_TABLES WHERE ID=:id;\n"
                      "DELETE FROM SYS_COLUMNS WHERE TABLE_ID=:id;\n"

                      "OPEN idx;\n"
                      "WHILE 1 = 1 LOOP\n"
                      "  FETCH idx INTO iid;\n"
                      "  IF (SQL % NOTFOUND) THEN EXIT; END IF;\n"
                      "  DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n"
                      "  DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n"
                      "END LOOP;\n"
                      "CLOSE idx;\n"

                      "END;\n", this);
}

/** Commit the transaction, possibly after drop_table().
@param deleted   handles of data files that were deleted */
void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
{
  ut_ad(dict_operation);
  commit_persist();
  if (dict_operation)
  {
    std::vector<uint32_t> space_ids;
    space_ids.reserve(mod_tables.size());
    ut_ad(dict_sys.locked());
    lock_sys.wr_lock(SRW_LOCK_CALL);
    mutex_lock();
    lock_release_on_drop(this);
    ut_ad(UT_LIST_GET_LEN(lock.trx_locks) == 0);
    ut_ad(ib_vector_is_empty(autoinc_locks));
    mem_heap_empty(lock.lock_heap);
    lock.table_locks.clear();
    /* commit_persist() already reset this. */
    ut_ad(!lock.was_chosen_as_deadlock_victim);
    lock.n_rec_locks= 0;
    while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
    {
      UT_LIST_REMOVE(lock.evicted_tables, table);
      dict_mem_table_free(table);
    }
    dict_operation= false;
    id= 0;
    mutex_unlock();

    for (const auto &p : mod_tables)
    {
      if (p.second.is_dropped())
      {
        dict_table_t *table= p.first;
        dict_stats_recalc_pool_del(table->id, true);
        dict_stats_defrag_pool_del(table, nullptr);
        if (btr_defragment_active)
          btr_defragment_remove_table(table);
        const fil_space_t *space= table->space;
        ut_ad(!p.second.is_aux_table() || purge_sys.must_wait_FTS());
        dict_sys.remove(table);
        if (const auto id= space ? space->id : 0)
        {
          space_ids.emplace_back(id);
          pfs_os_file_t d= fil_delete_tablespace(id);
          if (d != OS_FILE_CLOSED)
            deleted.emplace_back(d);
        }
      }
    }

    lock_sys.wr_unlock();

    mysql_mutex_lock(&lock_sys.wait_mutex);
    lock_sys.deadlock_check();
    mysql_mutex_unlock(&lock_sys.wait_mutex);

    for (const auto id : space_ids)
      ibuf_delete_for_discarded_space(id);
  }
  commit_cleanup();
}