Quantcast
Channel: Insight for DBAs Archives - Percona Database Performance Blog
Viewing all articles
Browse latest Browse all 1365

Why You Should Avoid Using “CREATE TABLE AS SELECT” Statement

$
0
0
Create Table As Select

Create Table As SelectIn this blog post, I’ll provide an explanation why you should avoid using the CREATE TABLE AS SELECT statement.

The SQL statement “create table <table_name> as select …” is used to create a normal or temporary table and materialize the result of the select. Some applications use this construct to create a copy of the table. This is one statement that will do all the work, so you do not need to create a table structure or use another statement to copy the structure.

At the same time there are a number of problems with this statement:

  1. You don’t create indexes for the new table
  2. You are mixing transactional and non-transactional statements in one transaction. As with any DDL, it will commit current and unfinished transactions
  3. CREATE TABLE … SELECT is not supported when using GTID-based replication
  4. Metadata locks won’t release until the statement is finished

When CREATE TABLE AS SELECT statement can break things very badly

Let’s imagine we need to transfer money from one account to another (classic example). But in addition to just transferring funds, we need to calculate fees. The developers decide to create a table to perform a complex calculation.

Then the transaction looks like this:

begin;
update accounts set amount = amount - 100000 where account_id=123;
-- now we calculate fees
create table as select ... join ...
update accounts set amount = amount + 100000 where account_id=321;
commit;

The “create table as select … join … ” commits a transaction that is not safe. In case of an error, the second account obviously will not be credited by the second account debit that has been already committed!

Well, instead of “create table … “, we can use “create temporary table …” which fixes the issue, as temporary table creation is allowed.

GTID issue

If you try to use CREATE TABLE AS SELECT when GTID is enabled (and ENFORCE_GTID_CONSISTENCY = 1) you get this error:

General error: 1786 CREATE TABLE ... SELECT is forbidden when @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1.

The application code may break.

Metadata lock issue

Metadata lock issue for CREATE TABLE AS SELECT is less known. (More information about the metadata locking in general)Please note: MySQL metadata lock is different from InnoDB deadlock, row-level locking and table-level locking.

This quick simulation demonstrates metadata lock:

session1:

mysql> create table test2 as select * from test1;

session2:

mysql> select * from test2 limit 10;

— blocked statement

This statement is waiting for the metadata lock:

session3:

mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------
| Id | User | Host      | db   | Command | Time | State                           | Info
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------
|  2 | root | localhost | test | Query   |   18 | Sending data                    | create table test2 as select * from test1
|  3 | root | localhost | test | Query   |    7 | Waiting for table metadata lock | select * from test2 limit 10
|  4 | root | localhost | NULL | Query   |    0 | NULL                            | show processlist
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------

The same can happen another way: a slow select query can prevent some DDL operations (i.e., rename, drop, etc.):

mysql> show processlistG
*************************** 1. row ***************************
           Id: 4
         User: root
         Host: localhost
           db: reporting_stage
      Command: Query
         Time: 0
        State: NULL
         Info: show processlist
    Rows_sent: 0
Rows_examined: 0
    Rows_read: 0
*************************** 2. row ***************************
           Id: 5
         User: root
         Host: localhost
           db: test
      Command: Query
         Time: 9
        State: Copying to tmp table
         Info: select count(*), name from test2 group by name order by cid
    Rows_sent: 0
Rows_examined: 0
    Rows_read: 0
*************************** 3. row ***************************
           Id: 6
         User: root
         Host: localhost
           db: test
      Command: Query
         Time: 5
        State: Waiting for table metadata lock
         Info: rename table test2 to test4
    Rows_sent: 0
Rows_examined: 0
    Rows_read: 0
3 rows in set (0.00 sec)

As we can see, CREATE TABLE AS SELECT can affect other queries. However, the problem here is not the metadata lock itself (the metadata lock is needed to preserve consistency). The problem is that the metadata lock will not be released until the statement is finished. 

The fix is simple: copy the table structure first by doing “create table new_table like old_table”, then do “insert into new_table select …”. The metadata lock is still held for the create table part (very short), but isn’t for the “insert … select” part (the total time to hold the lock is much shorter). To illustrate the difference, let’s look at two cases:

  1. With “create table table_new as select … from table1“, other application connections can’t read from the source table (table1) for the duration of the statement (even “show fields from table1” will be blocked)
  2. With “create table new_table like old_table” + “insert into new_table select …”, other application connections can’t read from the source table during the “insert into new_table select …” part.

In some cases, however, the table structure is not known beforehand. For example, we may need to materialize the result set of a complex select statement, involving joins and/or group by. In this case, we can use this trick:

create table new_table as select ... join ... group by ... limit 0;
insert into new_table as select ... join ... group by ...

The first statement creates a table structure and doesn’t insert any rows (LIMIT 0). The first statement places a metadata lock. However, it is very quick. The second statement actually inserts rows into the table and doesn’t place a metadata lock.

More reading on metadata locks and how to troubleshoot them in MySQL 5.7: 

Quickly Troubleshoot Metadata Locks in MySQL 5.7

 

 


Viewing all articles
Browse latest Browse all 1365

Trending Articles