WL#6049: Meta-data locking for FOREIGN KEY tables

Affects: Server-8.0   —   Status: Complete

This worklog consists of two parts:
1) Acquiring metadata locks on tables across foreign key relationships so
   that conflicting operations are blocked.
2) Updating FK metadata if a parent table changes.

Currently, InnoDB handles FOREIGN KEY constraints internally. This complicates a
few tasks, because MySQL meta-data locking is handled at the SQL layer 
and will not protect the FOREIGN KEY parent tables of a table during statement
execution. E.g. the parent table can be dropped while a InnoDB is running a
foreign key check. Taking metadata locks is required to fix these issues.

Also, foreign key definitions in the new DD are now not updated
if the parent changes. This is because FK metadata is owned by the child and
thus needs the child to be locked to be updated safely. Doing such FK updates
when the parent changes, will also be part of this worklog.


F1: Operations which insert or update rows in the child table of foreign
    key and which because of this have to check for presence of matching
    values in parent table must:
    1) be blocked by already running concurrent DDL statement on the parent
       table, provided that it reached the point where such statement
       acquires strong locks on the table.
    2) block concurrent DDL statements on the parent table which update
       its metadata until the end of transaction.
    3) be compatible with READ lock acquired on parent table by LOCK TABLES
       statement in other connection. They also must be compatible with
       ALTER TABLE ...ALGORITHM=COPY copying phase carried out on parent
       table in other connection (but not with metadata update phase of
       such ALTER TABLE).
    4) be incompatible (i.e. be blocked or block) with WRITE lock acquired
       on parent table by LOCK TABLES statement in other connection.
    5) in cases when FK check is not necessary due to FOREIGN_KEY_CHECKS=0
       mode the concurrent DDL statements on parent table can be allowed
       (but not LOCK TABLES WRITE).

F2: Operations which update or delete rows in the parent table of foreign
    key and which because of this have to check for absence of matching
    values in or carry out other referential action on child table must:
    1) be blocked by already running concurrent DDL statement on the child
       table, provided that it reached the point where such statement
       acquires strong locks on the table.
    2) block concurrent DDL statements on the child table which update
       its metadata until the end of transaction.
    3) for FKs with NO ACTION or RESTRICT as refential actions be compatible
       with READ lock acquired on child table by LOCK TABLES statement in
       other connection.
    4) for FKS with CASCADE/SET NULL and SET DEFAULT as referential actions
       be incompatible (i.e. be blocked or block) with READ lock acquired
       on child table by LOCK TABLES statement in other connection.
    5) be incompatible (i.e. be blocked or block) with WRITE lock acquired
       on child table by LOCK TABLES statement in other connection.
    6) in cases when FK check/referential action is not necessary due to
       FOREIGN_KEY_CHECKS=0 mode the concurrent DDL statements on child
       table can be allowed (but not LOCK TABLES WRITE statements).

F3: DDL statements which add or delete foreign key, such as CREATE TABLE,
    CREATE TABLE ... SELECT, ALTER TABLE ... ADD/DROP FOREIGN KEY,
    DROP TABLE/DATABASE deleting child table, must block concurrent DDL
    and DML on its parent table before they reach their conceptual commit
    point (or be blocked if concurrent statement has started earlier and
    reached certain point in its execution).

F4: DDL statements which change parent table definition and because of
    this change some metadata associated with FK must block concurrent
    DDL and DML on its child table before they reach their conceptual
    commit point (or be blocked if concurrent statement has started
    earlier and reached certain point in its execution). The examples
    of such statements are ALTER TABLE ... RENAME and RENAME TABLES
    on parent table; ALTER TABLE on the parent table which changes name
    of the unique constraint used by FK; CREATE TABLE variants, ALTER
    TABLE ... RENAME and RENAME TABLES which add valid parent table to
    FK when proper parent table was missing (this is possible in
    FOREIGN_KEY_CHECKS=0 mode); DROP TABLES which removes parent table
    without deleting child (again this is possible in FOREIGN_KEY_CHECKS=0
    mode).
    
F5: ALTER TABLE ... ADD FOREIGN KEY must block concurrent DDL and LOCK
    TABLES WRITE on the parent table (or be blocked by them if they
    have started earlier) starting from the beginning of the phase
    when foreign key being added is checked (this is table copying
    phase for ALTER TABLE ... ALGORITHM=COPY and main in-place phase
    for ALTER TABLE ... ALGORITHM=INPLACE) until end of statement.

F6: CREATE TABLE variants and ALTER TABLE which add new FK must correctly
    evaluate name of unique constraint in the parent table which will be
    used for FK and store this value in UNIQUE_CONSTRAINT_NAME column of
    mysql.foreign_keys data-dictionary table. If there is no such
    constraint, e.g. because parent table is missing (this is possible
    in FOREIGN_KEY_CHECKS=0 mode) or because non-unique key will be
    used (InnoDB allows this) NULL value must be stored.

F7: When CREATE TABLE variant, RENAME TABLES or ALTER TABLE ... RENAME
    add parent table for the FK which previously missed it (orphan FKs
    are possible in FOREIGN_KEY_CHECKS=0 mode) they need to set value
    of UNIQUE_CONSTRAINT_NAME column in row describing FK in the 
    mysql.foreign_keys data-dictionary table to the name of unique
    constraint in the parent table which will be used for FK checks
    or to NULL if no such unique constraint exists.

F7: When ALTER TABLE on the parent table for the FK changes table
    definition in such a way that name of unique constraint used
    for FK checks is changed (e.g. by renaming it, creating, deleting
    or re-creating constraint) this change of name should be
    correctly reflected in value of UNIQUE_CONSTRAINT_NAME column
    for row describing FK in mysql.foreign_keys data-dictionary
    table. NULL should be used if no such unique constraint exists
    as result of the ALTER TABLE.

F8: In cases when parent table for the FK is dropped without dropping
    the child table (this is possible when DROP TABLE or DROP DATABASE
    which delete parent table are executed in FOREIGN_KEY_CHECKS=0 mode)
    value of UNIQUE_CONSTRAINT_NAME column in row describing this FK
    in the mysql.foreign_keys data-dictionary table must be set to NULL.

F9: ALTER TABLE ... RENAME and RENAME TABLES which change the name of
    parent table for the FK must update REFERENCED_TABLE_SCHEMA and
    REFERENCED_TABLE_NAME values for row describing this FK in the
    mysql.foreign_keys data-dictionary table to reflect new parent
    schema and table name.

NF1: Implementation of this changes should not cause serious degradation
     of performance and scalability for DML workloads involving tables with
     foreign keys (the exact margin to be allowed to be determined after
     initial benchmarks).
The primary goal of this task is to ensure that foreign keys checks,
which are performed by storage engine (e.g. InnoDB), do not access
tables which are concurrently modified by DDL statements.

N0: Note that it is not a goal of this task to move FK processing
    to the SQL-layer.

Such an isolation of FK checks/actions from DDL statements can be
achieved by ensuring that FKs are taken into account when we acquire
metadata "operation-type aware" locks for DML and DDL statements.

This requires the following steps:

1) We should ensure that DML statements acquire appropriate metadata
   locks on all tables which they implicitly use for foreign key
   processing.

   *) If some statement is going to update or insert into a child
      table in a FK relationship, it should also acquire a SR lock
      on its parent as it is  going to be used for checks.
   *) If some statement updates or deletes rows from a parent table in
      a FK relationship, then it should also acquire SR or SW lock on
      the child table. The type of lock in this case depends on the
      referential action specified for this foreign key. If it is
      NO ACTION or RESTRICT, then the child table is going to be used
      for check only and SR lock is sufficient. If the FK has SET NULL,
      SET DEFAULT or CASCADE as referential action, then the statement
      might have to update child table and therefore SW lock on it is
      required.
   *) The above rules should be applied recursively in order to
      correctly handle situation when a cascading update or delete
      should trigger more checks or actions due to fact that child
      table serves as parent (or child) in a foreign key other than
      the one being currently processed.
   *) These rules should also take into account the value of the
      @@foreign_key_checks system variable which allows disabling of
      foreign keys check/actions for a particular connection. In this
      case there is no need to lock additional tables.

   We implement these rules by extending existing prelocking algorithm/
   process. Note that we don't plan to do full-blown table opens for
   parent/child tables on which MDL locks are acquired because of FKs.
   Instead we will acquire dd::Table
   objects for child tables which are going to be modified by cascading
   action and thus can cause more FK checks/actions.
  
   Also note that in theory it should be enough to acquire S lock to
   prevent concurrent DDL on parent/child tables. However, acquiring
   SR and SW locks as appropriate allows to properly isolate DML with
   FK checks/actions from concurrent LOCK TABLES on MDL layer. So we
   prefer the latter option.

2) DDL statements which add or drop foreign keys to the system (i.e.
   CREATE TABLE variants and ALTER TABLE ADD/DROP FOREIGN KEY) should
   acquire X lock on FK parent table before child table definition
   changes. This is because we want to guarantee that there can be no
   races between a DML statement, which analyzes foreign keys in which
   the table serves as a parent during building of the prelocking set
   and a DDL statement that adds/drops a foreign key referencing the
   table being analyzed by the first statement.

3) DDL statements which change parent table in FK might need to update
   FK metadata (e.g. RENAME TABLE on parent or ALTER TABLE which renames
   parent key of FK). To avoid races and to follow DD protocol such
   statements need to acquire X lock on FK child table before parent
   table definition changes.

4) DDL statements which add foreign keys to the system, like ALTER TABLE
   ADD FOREIGN KEY, or even simply check them need to get parent table's
   metadata and need to read data from it. Consequently, we need to
   acquire SR lock on parent table to ensure that is not being changed by
   concurrent DDL and to correctly isolate our DDL from concurrent LOCK
   TABLES.


The secondary goal of this task is to ensure that DDL on parent tables
correctly updates foreign key metadata. Specifically we need to update
the following metadata:

I)   Name of unique constraint in parent table for the FK.

     Current code misuses DD.FOREIGN_KEYS.UNIQUE_CONSTRAINT_ID to store
     id of supporting index for the FK in the child table. This is not
     what this column intended originally and will block implementation
     of I_S.REFERENTIAL_CONSTRAINTS as view on top of data-dictionary.
     
     This WL will replace this column with VARCHAR(64) fieldd which
     will store name of unique key in the parent table used for the FK --
     DD.FOREIGN_KEYS.UNIQUE_CONSTRAINT_NAME (storing id of unique key is
     technically possibly but is more cumbersome and requires extra join
     in I_S.REFERENTIAL_CONSTRAINTS implementation). Since InnoDB allows
     existence of child table and referencing non-unique keys we will
     make it NULLable and will store NULL value if parent key is non-unique
     or parent table doesn't exist.

     To keep UNIQUE_CONSTRAINT_NAME updated we need to:

     - Correctly evaluate its value when FK is added by CREATE TABLE or
       ALTER TABLE.
     - Replace NULL value with correct value when there is FK with
       missing parent table (this is possible if FOREIGN_KEY_CHECKS=0)
       and parent table is created (either by one of CREATE TABLE variants
       or by ALTER TABLE RENAME/RENAME TABLE).
     - Set it to NULL value when parent table is dropped by DROP TABLE or
       DROP DATABASE and child table is not (again this is only possible
       if FOREIGN_KEY_CHECKS=0).
     - Update name (possibly setting NULL value) if ALTER TABLE on
       parent table adds, drops of otherwise changes parent key for
       the FK.
     
II)  Referenced schema and table names (DD.FOREIGN_KEYS.REFERENCED_TABLE_SCHEMA
     and DD.FOREIGN_KEYS.REFERENCED_TABLE_NAME).

     Renaming of parent table by ALTER TABLE RENAME or RENAME TABLES need
     to update these fields to reflect new table names.

III) This WL is not going to update of name of referenced parent columns
     (DD.FOREIGN_KEY_COLUMN_USAGE.REFERENCED_COLUMN_NAME) when these
     columns are renamed in parent.
     
1) Changes to DML implementation
================================

We extend prelocking algorithm to take into account foreign keys
according to rules which were described in HLS.

a) We want to avoid processing of the same table in the
   same role towards FKs (and thus the same FKs!) by prelocking
   algorithm twice. To do this we will need to add  tuples already processed by algorithm
   to the hash representing prelocking set (currently called
   "Query_tables_list::sroutines").

   This is to be done by new add_used_fk_table() helper function
   which will also check if specific element is already present
   in the set.

   We introduce an enum with values representing various roles
   of table towards FKs in the statement:

   enum fk_table_role {
     /**
       There was insert or update on one of child tables.
       We will need to lookup new values in parent, so need
       to acquire SR lock on it.
      */
     FK_TABLE_ROLE_PARENT_CHECK= 0,
     /**
       There was delete or update on a parent table for FK which
       has RESTRICT/NO ACTION as corresponding rule. We will need
       to check if old parent key was referenced by child table,
       so need to acquire SR lock on it.
     */
     FK_TABLE_ROLE_CHILD_CHECK
     /**
       There was update on a parent table for FK with CASCADE,
       SET NULL, SET DEFAULT as on update rule or delete on a
       parent table for FK with SET NULL, SET DEFAULT as on delete
       rule. We might need to update rows in child table, so we need
       to acquire SW lock on it. We also need to take into account
       that child table might be parent for some other FKs, so
       such update needs to be handled recursively.
     */
     FK_TABLE_ROLE_CHILD_UPDATE,
     /**
       There was delete on a parent table for FK with CASCADE
       We might need to delete rows from child table, so we need to
       acquire SW lock on it. We also need to take into account
       that child table might be parent for some other FKs, so
       such update needs to be handled recursively.
     */
     FK_TABLE_ROLE_CHILD_DELETE};

   to be used as argument to this function.

   We need to adjust code dealing with "Query_tables_list::sroutines"
   hash to avoid construction of full-blown MDL_key just ot to identify
   objects in prelocking set.

   N1: To be able take into account that cascading actions update
       only subset of columns and thus in many cases do not require
       more cascading actions we need to use 
       in "Query_tables_list::sroutines" hash instead, as in this
       case it is important to differentiate actions on child tables
       which are caused by updates/deletes on parent tables of
       different FKs.

b) New process_table_fks() helper function is introduced
   with thefollowing logic:

  process_table_fks(..., table, is_insert, is_update, is_delete)
  {
    if ((@@foreign_key_checks are ON) && (table has foreign keys))
    {
      Mark statement as requiring prelocking if not already.

      for (Each foreign key in which this table is a child.)
      {
        if ((table is INSERTed into or UPDATED))
        {
          add_used_fk_table(parent_db, parent_name,
                            FK_TABLE_ROLE_PARENT_CHECK);
        }
      }

      for (Each foreign key in which this table is a parent.)
      {
        if (table is UPDATED &&
            on update rule is NO ACTION or RESTRICT ||
            table is DELETED &&
            on delete rule is NO ACTION or RESTRICT)
        {
          add_used_fk_table(child_db, child_table,
                            FK_TABLE_ROLE_CHILD_CHECK);
        }
        if (table is UPDATED &&
            on update rule is SET NULL, CASCADE or SET DEFAULT ||
            table is DELETED &&
            on delete rule is SET NULL, SET DEFAULT)
        {
            add_used_fk_table(child_db, child_table,
                              FK_TABLE_ROLE_CHILD_UPDATE);
        }
        if (table is DELETED && on delete rule is CASCADE)
        {
          add_used_fk_table(child_db, child_table,
                            FK_TABLE_ROLE_CHILD_DELETE);
        }
      }
    }
  }

c) We extend DML_prelocking_strategy::handle_table() method
   according to the following pseudo-code:

DML_prelocking_strategy::handle_table(... table ...)
{
  if (table is changed)
  {
    if (triggers are present)
    {
      // handle triggers
      ...
    }

+   process_table_fks(..., table, table is INSERT, is UPDATE, is DELETED)
  
  }
}

d) We extend open_and_process_routine() function
   according to the following pseudo-code:

open_and_process_routine(..., sroutines entry, ...)
{
  switch (sroutines entry type)
  {
  case FUNCTION:
  ...
  case PROCEDURE:
  ...
  case TRIGGER:
  ...
+ case FK_TABLE_ROLE_PARENT_CHECK:
+ case FK_TABLE_ROLE_CHILD_CHECK:
+
+   acquire MDL_SHARED_READ with MDL_TRANSACTION duration on
+   db_name(), entry->table_name()> table (might be
+   parent or child depending on entry type).
+
+   break;
+ case FK_TABLE_ROLE_CHILD_UPDATE:
+ case FK_TABLE_ROLE_CHILD_DELETE:
+
+   acquire MDL_SHARED_WRITE with MDL_TRANSACTION duration on
+   db_name(), entry->table_name()> table (always 
+   child table).
+
+   Acquire dd::Table for db_name(), entry->table_name()>
+ 
+   process_table_fks(..., child table,
+                     is NOT INSERT,
+                     entry type == FK_TABLE_ROLE_PARENT_UPDATE ?
+                     is UPDATE : is DELETE)
+   break;
  default:
  ...
  }
}

e) Cases for FK_TABLE_ROLE_CHILD_UPDATE/DELETE in open_and_process_routine()
   also need to take into account the fact that prelocking set is normally
   not rebuilt during prepared statement re-executions. So it might become
   invalid if definition of child table changes. To handle this fact we
   will use versioning scheme similar to one which is used for stored
   routines (see Sroutine_hash_entry::m_sp_cache_version) and tables
   (see TABLE_LIST::m_table_ref_version) which will trigger prepared
   statement re-prepare if table definition changes.

f) To preserve the expectation that table locked with LOCK TABLES WRITE
   can be modified without further waits we need to ensure that LOCK
   TABLES WRITE on the table also acquires strong locks on tables
   associated with it through FKs. Prelocking algorithm extension that
   handles FKs need to acquire SRO metadata lock instead of SR lock and
   SNRW lock instead of SW lock for LOCK TABLES statements.

N2: Acquisition of dd::Table objects in the above code and table version
    checks in e) might have negative impact on scalability and performance.
    This impact should be measured by QA for this WL. If it turns out to
    be too significant we might have to take some steps to alleviate it.

 
2) Changes to DDL implementation
================================

Code in mysql-trunk stores information about foreign keys added by
CREATE TABLE or ALTER TABLE statements even if table's storage engine
doesn't support them. Following this approach complicates DDL
implementation and will create problems for I_S.REFENTIAL_CONSTRAINTS
implementation on top of the data-dictionary. This WL will change the DDL
code (prepare_create_table() and mysql_prepare_alter_table() specifically)
to avoid storing information about FKs on tables which do not support them.

Also, currently we do not support cross-SE foreign keys. The code should
take this into account and do not treat, e.g. CREATE TABLE which adds
parent table to some pre-existing orphaned child table in different SE
as restoration of valid FK.

Also, at the moment, we don't have any in-house SEs which support foreign
keys but do not support atomic DDL. Assuming that FKs can only be added
to tables in SEs supporting atomic DDL allows to simplify the code (e.g.
in CREATE TABLE implementation), so this WL will do this. Indeed, there
are cases in which DDL behavior can't be fully atomic (e.g. cross-SE
ALTER TABLE which also adds FKs) which still might require special handling.
The code can be adjusted in future once there is need to support FKs for
non-atomic DDL SEs (for example, due 3rd-party SEs or new in-house SEs).

Note that DDL statements dealing with FKs have to acquire strong locks
on tables in order which is not always predictable. This means that
there is a chance of deadlocks related to these locks acquisition.
The problem can be alleviated in some cases by implementing some kind
of back-off and retry technique for DDL statements (or integrating
acquisition of these locks into open_and_lock_tables() call when
appropriate). However, we have decided to postpone implementation of
this feature until there is better understanding of how severe problem
is for our users.

2.1) Simple CREATE TABLE
------------------------
As described in HLS this statement needs to acquire X lock on all
parent tables participating in newly added foreign keys.
We also need to acquire X lock on all child tables which have FKs
referencing newly created table (which belong to same SE) since
we are going to update unique constraint names for these FKs (this
is possible if these child tables were created with
@@foreign_key_checks == 0 mode).

N3: Note that it is important to allow creation of foreign keys
    referencing non-existing table in @@foreign_key_checks == 0
    mode as mysqldump relies on this feature when restoring tables
    with foreign keys.

N4: We should not acquire any strong locks if table already exists.
    From our past experience it seems that people expect this.

In addition to changes in MDL handling we need to add code
which will update unique constraint names for FKs which
reference newly created table.

Also after CREATE TABLE is committed we need to invalidate DD
entries for parent tables to force their reload in order to get
them updated with information about FKs we have added.
Alternatively, this can be done right before commit when we
can be sure that there will be no accesses to parent tables.


2.2) CREATE TABLE LIKE
----------------------
Doesn't copy foreign key constraints and thus doesn't require
any additional locking on parent tables.

N5: It makes sense to change this. But since this is incompatible
    change separate FR to be filed.

We still need to acquire X lock on all child tables which have FKs
referencing newly created table to update unique constraint name
for such FKs. Naturally, code implementing such update should be
added too.

Similarly to simple CREATE TABLE no strong locks should be acquired
and updates should not happen if target table already exists.


2.3) CREATE TABLE SELECT
------------------------
Similarly to simple CREATE TABLE we need to acquire X lock
on all tables which serve as parents in FKs. We also need
to acquire X lock on all child tables for FKs which happen
to reference newly created table so we can update unique
constraint name for these FKs.

However, unlike simple CREATE TABLE, CREATE TABLE SELECT can
take quite some time due to rows being selected and inserted,
so acquiring these locks at the statements should be avoided
and postponed until before the point where CTS is conceptually
committed.

Since during insertion of rows into newly created table
implicit foreign key checks are to occur we also need to
acquire at least SR lock on parent tables for the duration of
these checks. In practice, to avoid some of potential deadlocks
due to concurrent DDL on parent tables, it makes sense to acquire
SU locks instead.

N6: Here we rely on the fact that InnoDB acquires row locks
    during FK checks. So while rows are inserted into target
    table we can check existence of matching records in parent
    tables, and rely on results of such check even though
    DML operations on parent tables won't see these FKs until
    DDL is committed.
    E.g AFAIR the same scheme was not acceptable for Falcon...

Update of unique constraint names for FKs which reference newly
created table needs to happen after X locks are acquired before
CTS conceptual commit point.

Similarly to simple CREATE TABLE, CREATE TABLE SELECT needs to
invalidate DD entries for parent tables to force their reload
in order to get them updated with information about FKs it has
added. It is convenient to do this after DDL commit or right
before it when we can be sure that there will be no additional
accesses to parent tables.


2.4) ALTER TABLE
----------------
ALTER TABLE ADD/DROP FOREIGN KEY variants affect FKs directly,
so they require acquiring of X lock on parent tables of FKs
being added/dropped before metadata update. 

ALTER TABLE RENAME affects FKs indirectly in several ways:
*) It will result in update of metadata for FKs which reference
   table being altered - referenced schema/table name needs
   to be updated, also by doing this rename we might add
   parent table for FKs which previously missed it, so we might
   need to update unique constraint name for such FKs.
   As result we need to acquire X lock on all child tables
   before updating metadata.
*) It also needs to acquire X lock on parent tables of all
   FKs from the table being altered, as otherwise we won't
   be able to cache metadata about these FKs in the description
   of parent tables correctly.

Other ALTER TABLE variants which affect unique keys might
affect FKs indirectly too by requiring updates of unique
constraint names for FKs which reference current table.
So they need to acquire X locks on child tables for FKs
which reference table being altered before updating metadata
for these FKs.

Similarly to CTS it makes sense to acquire these X locks
not at the start of the statement but just before we update
metadata for altered table.

Also similarly to CTS, ALTER TABLE variants which add (or
simply check) FKs have to acquire SU locks on parent tables.

N7: Currently ALTER TABLE COPY variant acquires SRO lock on
    all parent tables. This seems to be no longer necessary
    thanks to changes in InnoDB SE. We need to check this
    and remove this code.

After exclusive locks on all related tables have been acquired
and new definition of table was installed but prior to statement
commit, we need to update unique constraint names for FKs
referencing this table if change of table definition has caused
change of these names. In case of ALTER TABLE RENAME we also need
to adjust name of parent tables for FKs which reference our table.
Plus we need to update unique constraint names for pre-existing
FKs which reference to new table name and thus got proper parent
table.

Finally, after commit or right before it we need to invalidate
DD objects for all parent tables for FKs which were added or
dropped, so entries for these tables are reloaded and reflect
updated information about FKs from table which was altered.

Unlike CREATE TABLE variants ALTER TABLE is allowed under LOCK
TABLES. So we need to ensure that set of tables prelocked by
LOCK TABLES keeps FK-related invariants valid after ALTER TABLE.
This is to be achieved by requiring that for ALTER TABLE executed
under LOCK TABLES all parent tables for new FKs need to be already
locked in SRO mode. We will also prohibit ALTER TABLE RENAME under
LOCK TABLES on any table which participate or going to participate
in FK. Such ALTER unlocks the table at its end and thus is very
likely to break FK invariants for set of locked tables.

N8: This is rather severe restriction which we should consider
    lifting eventually (e.g. by keeping table locked in this case).

2.5) DROP TABLES/DROP DATABASE
------------------------------
We need to acquire X lock on parent tables in FKs for
tables being dropped. This is necessary to be able to
safely update cached info about FKs associated with
parent table DD objects.

We also need to acquire X lock on child tables in FKs
which reference tables being dropped, so we can update
unique constraint names for such FKs (this is only
possible if @@foreign_key_checks=0 as otherwise attempt
to drop parent table will fail).

We need to carry out the above-mentioned update along with
dropping table definitions from DD.

After the statement commit or right before it we need to invalidate
DD objects for all parent tables in FKs which child tables were
dropped. Reload of DD entries for these tables will ensure that
they reflect the fact that they no longer have FKs from dropped
tables.

DROP TABLES is also allowed under LOCK TABLES and therefore
such statement needs to take care about FK-related invariants
for set of tables prelocked by LOCK TABLES. We should not
release MDL on tables dropped (as we normally do) if these
tables participate as parents in some FKs and their child
tables are still kept locked in write mode (this is possible
with FOREIGN_KEY_CHECKS=0).

2.6) RENAME TABLES
------------------
Similarly to ALTER TABLE RENAME, we need to acquire X locks
on all child tables for FKs which reference table being
renamed, as we need to update info about referenced
schema/table name and possibly unique constraint names.
We also need to acquire X locks on all parent tables for
FKs in table being renamed to be able safely invalidate
these tables in the DD cache.

After all locks are acquired and table is renamed in SE
and data dictionary but before commit happens we need to
update child tables for FKs which reference renamed table
to reflect new schema/table name. We also need to update
unique constraint name for formerly orphan FKs which 
have new table name as parent table.

Then after statement commit or right before it we need to
invalidate DD cache objects for all parent tables in FKs
which have one of renamed tables as child. Again this
allows to ensure that DD objects for these tables are
reloaded and updated with new child table names as result.

N9: When renaming a mix of table in SEs with different level
    of support for DDL atomicity, we are facing rollback by
    compensation. When reverting the FK changes, we cannot
    re-establish the proper before image for tables that were
    FK orphans, because this information is lost in the
    rename process. For proper treatment, we should keep hold
    of the necessary information from the before image.