introducing new sql syntax and improving performance with preparse query rewrite plugins

47
Preparse Query Rewrite Plugins New SQL syntax for fun & performance Sveta Smirnova Principal Support Engineer January, 30, 2016

Upload: sveta-smirnova

Post on 13-Apr-2017

722 views

Category:

Software


0 download

TRANSCRIPT

Preparse Query Rewrite PluginsNew SQL syntax for fun & performance

Sveta SmirnovaPrincipal Support Engineer

January, 30, 2016

Table of Contents

•Introducing new SQL syntax

•Working with results

•Variables

•Summary

2 www.percona.com

Introducing newSQL syntax

3 www.percona.com

MySQL often receives blames• From mascots and from humans

• It cannot make a toast• It does not support some syntax

4 www.percona.com

MySQL often receives blames

• From mascots and from humans

• It cannot make a toast

• It does not support some syntax

4 www.percona.com

MySQL often receives blames• From mascots and from humans• It cannot make a toast• It does not support some syntax

4 www.percona.com

Or does it?

• FILTER clause in MySQL on my homemachine

5 www.percona.com

Or does it?

• FILTER clause in MySQL on my homemachine

5 www.percona.com

Or does it?

• FILTER clause in MySQL on my homemachine

• But not in the user manual

5 www.percona.com

How is it done?

• With 181 lines of code

• Including comments!

• And new Query Rewrite Plugin interface

6 www.percona.com

A little bit of history

• First introduced in version 5.7.5

• Was available at MySQL Labs

• Two types of plugins

• Pre-parse

• Post-parse

7 www.percona.com

Today

• Part of Audit plugin interface• Step in at

• MYSQL AUDIT GENERAL ALL• MYSQL AUDIT CONNECTION ALL• MYSQL AUDIT PARSE ALL

• MYSQL AUDIT PARSE PREPARSE

• MYSQL AUDIT PARSE POSTPARSE

• MYSQL AUDIT AUTHORIZATION ALL• ...

8 www.percona.com

Plugin skeleton

#include <mysql/plugin.h>

#include <mysql/plugin_audit.h> - Audit plugin declaration

...

static MYSQL_PLUGIN plugin_info_ptr; - Pointer to the plugin

...

static int filter_plugin_init(MYSQL_PLUGIN plugin_ref); - Plugin initialization

...

static int filter(MYSQL_THD thd, mysql_event_class_t event_class,

const void *event); - Entry point for MYSQL_AUDIT_PARSE_PREPARSE

...

static st_mysql_audit filter_plugin_descriptor;

...

mysql_declare_plugin(filter_plugin);

9 www.percona.com

Plugin descriptor

static st_mysql_audit filter_plugin_descriptor= {

MYSQL_AUDIT_INTERFACE_VERSION, /* interface version */

NULL,

filter, /* implements FILTER */

// You can also use MYSQL_AUDIT_PARSE_ALL

{ 0, 0, (unsigned long) MYSQL_AUDIT_PARSE_PREPARSE,}

};

10 www.percona.com

Plugin declaration

mysql_declare_plugin(filter_plugin)

{

MYSQL_AUDIT_PLUGIN,

&filter_plugin_descriptor,

"filter_plugin",

"Sveta Smirnova",

"FILTER SQL:2003 support for MySQL",

PLUGIN_LICENSE_GPL,

filter_plugin_init,

NULL, /* filter_plugin_deinit - TODO */

0x0001, /* version 0.0.1 */

NULL, /* status variables */

NULL, /* system variables */

NULL, /* config options */

0, /* flags */

}

mysql_declare_plugin_end;

11 www.percona.com

Memory management for plugins#include <my_thread.h> // my_thread_handle needed by mysql_memory.h

#include <mysql/psi/mysql_memory.h>

...

static PSI_memory_key key_memory_filter;

static PSI_memory_info all_rewrite_memory[]=

{

{ &key_memory_filter, "filter", 0 }

};

static int filter_plugin_init(MYSQL_PLUGIN plugin_ref)

{

plugin_info_ptr= plugin_ref;

const char* category= "sql";

int count;

count= array_elements(all_rewrite_memory);

mysql_memory_register(category, all_rewrite_memory, count);

return 0; /* success */

}

12 www.percona.com

SQL:2003

<filter clause> ::=

FILTER <left paren> WHERE <search condition> <right paren>

(10.9 <aggregate function>, 5WD-02-Foundation-2003-09.pdf, p.505)

Only for aggregate functions:

<computational operation> ::=

AVG | MAX | MIN | SUM | EVERY | ANY

| SOME | COUNT | STDDEV_POP | STDDEV_SAMP

| VAR_SAMP | VAR_POP | COLLECT | FUSION | INTERSECTION

<set quantifier> ::=

DISTINCT

| ALL

MySQL only supports

COUNT | AVG | SUM | MAX | MIN

| STDDEV_POP | STDDEV_SAMP

| VAR_SAMP | VAR_POP

13 www.percona.com

Implementing FILTER clause

• FILTER is practically

CASE WHEN foo THEN bar ELSE NULL

• So we only need to catch

FUNCTION(var) FILTER(WHERE foo)

• And replace it with CASE

14 www.percona.com

Catching up the querystatic int filter(MYSQL_THD thd, // MySQL Thread object

mysql_event_class_t event_class, // Class of the event

const void *event // Event itself

)

{

const struct mysql_event_parse *event_parse=

static_cast<const struct mysql_event_parse *>(event);

if (event_parse->event_subclass != MYSQL_AUDIT_PARSE_PREPARSE)

return 0;

string subject= event_parse->query.str; // Original query

string rewritten_query;

//requires std::regex and GCC 4.9+

regex filter_clause_star("(COUNT)\((\s*\*\s*)\)\s+"

+ "FILTER\s*\(\s*WHERE\s+([^\)]+)\s*\)",

ECMAScript | icase);

rewritten_query= regex_replace(subject, filter_clause_star,

"$1(CASE WHEN $3 THEN 1 ELSE NULL END)");

...

15 www.percona.com

Rewritten query

void _rewrite_query(const void *event,

const struct mysql_event_parse *event_parse,

char const* new_query

)

{

char *rewritten_query= static_cast<char *>(my_malloc(

key_memory_filter, strlen(new_query) + 1, MYF(0)));

strncpy(rewritten_query, new_query, strlen(new_query));

rewritten_query[strlen(new_query)]= ’\0’;

event_parse->rewritten_query->str= rewritten_query; // Rewritten query

event_parse->rewritten_query->length=strlen(new_query);

// You must set this flag to inform MySQL Server what query was rewritten

*((int *)event_parse->flags)|=

(int)MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN;

}

16 www.percona.com

Working withresults

17 www.percona.com

Can we do better?

• Playing with syntax is fun

• But can we introduce something moreMySQL-ish?

18 www.percona.com

Custom hint plugin

• MySQL 5.7 has Optimizer HintsSELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1

FROM t3 WHERE f1 > 30 AND f1 < 33;

SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...;

SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...;

SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...;

EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...;

• But sometimes thread-specific buffersaffect query execution

• Workaround requires processing result setof each of these statements

• This is why I extended optimizer hintsyntax

19 www.percona.com

Custom hint plugin

• MySQL 5.7 has Optimizer Hints• But sometimes thread-specific buffers

affect query execution• Common workaround exists:

SET tmp_table_size=1073741824;

SELECT * FROM t1 INNER JOIN t2 WHERE ...;

SET tmp_table_size=DEFAULT;

• Workaround requires processing result setof each of these statements

• This is why I extended optimizer hintsyntax

19 www.percona.com

Custom hint plugin

• MySQL 5.7 has Optimizer Hints• But sometimes thread-specific buffers

affect query execution• Workaround requires processing result set

of each of these statements• This is why I extended optimizer hint

syntaxSELECT /*+ join_buffer_size=16384 */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33;

SELECT /*+ tmp_table_size=1073741824 BKA(t1) NO_BKA(t2) */ *

FROM t1 INNER JOIN t2 WHERE ...;

19 www.percona.com

New features

• For Custom Hints we need to:

• Store previous values of threadvariables we are going to modify

• Modify variables

• Revert them back before sending result

20 www.percona.com

Store previous values

// map to store modified variables

static map <my_thread_id, map<supported_hints_t, ulonglong> > modified_variables;

...

/* The job */

static int custom_hint(MYSQL_THD thd, mysql_event_class_t event_class,

const void *event)

{

...

// If we have a match store create map of thread variables

std::map<supported_hints_t, ulonglong> current;

...

After processing variables store them in modified_variables map

modified_variables[thd->thread_id()]= current;

...

21 www.percona.com

Modify variables

• Since we have access to MYSQL THDthis is easy:

switch(get_hint_switch(ssm[1]))

{

case JOIN_BUFFER_SIZE:

current[JOIN_BUFFER_SIZE]= thd->variables.join_buff_size;

thd->variables.join_buff_size= stoull(ssm[2]);

break;

case TMP_TABLE_SIZE:

current[TMP_TABLE_SIZE]= thd->variables.tmp_table_size;

thd->variables.tmp_table_size= stoull(ssm[2]);

break;

...

22 www.percona.com

Revert variables back

• First we need to tell plugin descriptor whatwe needMYSQL AUDIT GENERAL RESULTstatic st_mysql_audit custom_hint_plugin_descriptor= {

MYSQL_AUDIT_INTERFACE_VERSION, /* interface version */

NULL,

custom_hint, /* implements custom hints */

{ (unsigned long) MYSQL_AUDIT_GENERAL_RESULT, 0,

(unsigned long) MYSQL_AUDIT_PARSE_PREPARSE,

}

};

• Then revert variables before sending result• And, finally, erase stored values for current

thread:

23 www.percona.com

Revert variables back• First we need to tell plugin descriptor what

we needMYSQL AUDIT GENERAL RESULT

• Then revert variables before sending resultif (event_general->event_subclass == MYSQL_AUDIT_GENERAL_RESULT)

{

map<my_thread_id, map<supported_hints_t, ulonglong> >::iterator

current= modified_variables.find(thd->thread_id());

if (current != modified_variables.end())

{

for (map<supported_hints_t, ulonglong>::iterator it=

current->second.begin(); it!= current->second.end(); ++it)

{

switch(it->first)

{

case JOIN_BUFFER_SIZE:

thd->variables.join_buff_size= it->second;

break;

• And, finally, erase stored values for currentthread:

23 www.percona.com

Revert variables back

• First we need to tell plugin descriptor whatwe needMYSQL AUDIT GENERAL RESULT

• Then revert variables before sending result

• And, finally, erase stored values for currentthread:

modified_variables.erase(current);

23 www.percona.com

Before Custom Hint Pluginmysql> flush status;

Query OK, 0 rows affected (0.00 sec)

mysql> select count(*), sum(c) from

-> (select s, count(s) c from joinit where i < 1000000 group by s) t;

+----------+--------+

| count(*) | sum(c) |

+----------+--------+

| 737882 | 737882 |

+----------+--------+

1 row in set (24.70 sec)

mysql> show status like ’Created_tmp_disk_tables’;

+-------------------------+-------+

| Variable_name | Value |

+-------------------------+-------+

| Created_tmp_disk_tables | 2 | -- 2 temporary tables on disk

+-------------------------+-------+

1 row in set (0.00 sec)

24 www.percona.com

Custom Hint Plugin at workmysql> flush status;

Query OK, 0 rows affected (0.00 sec)

mysql> select /*+ tmp_table_size=134217728 max_heap_table_size=134217728 */

-> count(*), sum(c) from

-> (select s, count(s) c from joinit where i < 1000000 group by s) t;

+----------+--------+

| count(*) | sum(c) |

+----------+--------+

| 737882 | 737882 |

+----------+--------+

1 row in set, 2 warnings (6.21 sec) -- 4 times speed gain!

mysql> show status like ’Created_tmp_disk_tables’;

+-------------------------+-------+

| Variable_name | Value |

+-------------------------+-------+

| Created_tmp_disk_tables | 0 | -- No disk-based temporary table!

+-------------------------+-------+

1 row in set (0.00 sec)

25 www.percona.com

Variables

26 www.percona.com

BACKUP DATABASE plugin

• Very simple syntax

• mysql> BACKUP SERVER;

+---------------------------------+

| Backup finished with status OK! |

+---------------------------------+

| Backup finished with status OK! |

+---------------------------------+

1 row in set, 1 warning (42.92 sec)

• Supports many tools

• Needs to pass options

27 www.percona.com

BACKUP DATABASE plugin

• Very simple syntax• Supports many tools

• mysqldump• mysqlpump• mysqlbackup• XtraBackup - Planned!

• Needs to pass options

27 www.percona.com

BACKUP DATABASE plugin

• Very simple syntax

• Supports many tools

• Needs to pass options

• Credentials

• Backup directory

• Custom

27 www.percona.com

Customization: credentials

• We have access to• MYSQL_THR->security_context

• thd->security_context()->user().str

• Password still has to be in the configuration file, under

[client]

or

[toolname]

section

• System variables• Since we are interested in backing up local server we will use

mysqld_unix_port

28 www.percona.com

Customization: variables

• Global variables - Example only!

static MYSQL_SYSVAR_STR(backup_dir, backup_dir_value, PLUGIN_VAR_MEMALLOC,

"Default directory...", NULL, NULL, NULL);

static MYSQL_SYSVAR_ENUM(backup_tool, backup_tool_name,

PLUGIN_VAR_RQCMDARG, "Backup tool. Possible values:

mysqldump|mysqlbackup", NULL, NULL,

MYSQLDUMP, &supported_tools_typelib);

• Thread variables

• Add to plugin declaration

29 www.percona.com

Customization: variables

• Global variables - Example only!

• Thread variables

static MYSQL_THDVAR_STR(backup_dir, PLUGIN_VAR_MEMALLOC,

"Default directory...", NULL, NULL, NULL);

static MYSQL_THDVAR_ENUM(backup_tool, PLUGIN_VAR_RQCMDARG,

"Backup tool. Possible values:

mysqldump|mysqlbackup|mysqlpump", NULL, NULL,

MYSQLDUMP, &supported_tools_typelib);

...

• Add to plugin declaration

29 www.percona.com

Customization: variables• Global variables - Example only!• Thread variables• Add to plugin declaration

static struct st_mysql_sys_var *mysqlbackup_plugin_sys_vars[] = {

MYSQL_SYSVAR(backup_dir),

MYSQL_SYSVAR(backup_tool),

MYSQL_SYSVAR(backup_tool_basedir),

MYSQL_SYSVAR(backup_tool_options),

NULL

};

mysql_declare_plugin(mysqlbackup_plugin)

{

MYSQL_AUDIT_PLUGIN,

&mysqlbackup_plugin_descriptor,

"mysqlbackup_plugin",

...

NULL, /* status variables */

mysqlbackup_plugin_sys_vars, /* system variables */

...

29 www.percona.com

Summary

30 www.percona.com

More possibilities

• Custom locks

• Access to thread- and server-specificvariables

• Fine control at multiple steps of queryexecution

• More

31 www.percona.com

Code

• https://github.com/svetasmirnova/

• filter plugin

• custom hint plugin

• mysqlbackup plugin

32 www.percona.com

Place for your questions

???

34 www.percona.com

Thank you!

http://www.slideshare.net/SvetaSmirnova

https://twitter.com/svetsmirnova

35 www.percona.com