Heim  >  Artikel  >  Datenbank  >  MySQL:如何编写Information Schema Plugin_MySQL

MySQL:如何编写Information Schema Plugin_MySQL

WBOY
WBOYOriginal
2016-06-01 13:44:28908Durchsuche

bitsCN.com

 

转载请署名:印风

1. 什么是i_s plugin

在mysql里面,默认会有一个information schema(以下简写为i_s)数据库,用于记录一些与元数据或表的模式相关的信息,与其他数据库不一样,在data目录下,并没有为i_s建立文件夹,这说明,i_s并不是物理存在的,而是在需要的时候,才会临时创建。这就可以解释为什么i_s库中的表的记录总是无法删除或修改。

 

2.为什么使用i_s plugin

虽然i_s中定义了丰富的表,但通过i_s plugin,我们可以将其功能进行扩展,丰富其中的信息,比如,我们可以把关心信息以表的形式展现出来,可以通过引入MySQL的内核代码,来监控内核的运行状态,例如锁资源状态、线程状态、table cache状态等信息。客户端可以通过sql来过滤想要的内容,甚至,我们可以在plugin中通过cond来进行过滤,而无需在mysql层处理。

 

3.如何编写i_s plugin

1)之前已经介绍过的,这里不在赘述,在plugin间通用的包括:

a. plugin的声明;

b.添加系统变量(show /setvariables)

c.添加状态变量(show status)

 

2)初始化I_S插件

函数原型:name_init(void *p)

函数用于初始化插件,包括指定表的模式、创建表、构造表的函数指针等信息,指针p会指向一个结构体st_schema_table,如下表:

字段

类型

描述

table_name

const char*

mysql会自动对表赋予插件名,因此我们无需直接赋值

fields_info

ST_FIELD_INFO *

ST_FIELD_INFO类型的结构体数组,用于存储表的每一列的信息,如列名及类型等

create_table

TABLE *(*create_table)  (THD *thd, TABLE_LIST *table_list);

函数指针,用来创建TABLE结构体,所有的i_s表基本一致,mysql会自动赋值

fill_table

int (*fill_table) (THD *thd, TABLE_LIST *tables, COND *cond);

函数指针,用于向表中填充记录

old_format

int (*old_format) (THD *thd, struct st_schema_table *schema_table);

用于支持内建i_s表的show功能,无需关注

process_table

int (*process_table) (

THD *thd, TABLE_LIST *tables, TABLE *table,                        bool res,  LEX_STRING *db_name,

LEX_STRING *table_name);

仅用于内建i_s表

idx_field1, idx_field2

int

仅用于内建i_s表

hidden

bool

如果为true,则其中的数据只能通过show展现,由于i_s Plugin不支持show,无需关心此变量

i_s_requested_object

uint

仅用于内建i_s表

 

初始化的目的是为了填充结构体st_schema_table,从而确定表的定义,在查询表的时候,调用相应的函数进行记录填充。由于该结构体与内建的i_s表是公用的,因此一些字段我们可以直接忽略掉。在编写plugin的时候,我们需要填充的内容包括:

Ø  Fields_info

Ø  Fill_table

 

2).初始化表结构fields_info

Fields_info结构体为st_field_info

字段

类型

描述

field_name

const char*

列名,通常用大写表示

field_length

uint

当列的类型为varchar、text时表示字符数,列类型为blob时表示字节数,类型为float 或double时表示数字数,对于decima类型,值为precision*100+scale

field_type

enum enum_field_types

枚举类型,用于指定行类型,包括如下:

MYSQL_TYPE_TINY、

MYSQL_TYPE_SHORT、

MYSQL_TYPE_INT24、

MYSQL_TYPE_LONG、

MYSQL_TYPE_LONGLONG、

MYSQL_TYPE_TIME、

MYSQL_TYPE_DATE、

MYSQL_TYPE_DATETIME、MYSQL_TYPE_TIMESTAMP、MYSQL_TYPE_FLOAT、

MYSQL_TYPE_DOUBLE、MYSQL_TYPE_DECIMAL、MYSQL_TYPE_NEWDECIMAL、MYSQL_TYPE_TINY_BLOB、MYSQL_TYPE_MEDIUM_BLOB、MYSQL_TYPE_BLOB、MYSQL_TYPE_LONG_BLOB、MYSQL_TYPE_STRING,虽然类型很多,但在内置的i_s表中,只用到了MYSQL_TYPE_STRING, MYSQL_TYPE_LONGLONG, MYSQL_TYPE_LONG, MYSQL_TYPE_DECIMAL,  MYSQL_TYPE_DATETIME这几种类型,为了避免意外的分享,我们也尽量使用这几种。

value

int

未使用

field_flags

uint

用于表示列的属性,为MY_I_S_UNSIGNED表示列为unsigned类型,为MY_I_S_MAYBE_NULL 表示该列的值可能为NULL

这里需要注意,如果定义为MY_I_S_MAYBE_NULL类型,那么在填充表字段信息时,我们总需要首先调用:

(非空)tables->table->field[0]->set_notnull();

(为空)tables->table->field[0]->set_null();

 

old_name

const char*

open_method

uint

仅用于内建的i_s表

 

通常我们会预定义数组,以NULL列结束:

ST_FIELD_INFO  is_field[] = {

         {……},

         ……

{0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0}

}

 

3)fill_table()

函数原型:int fill_table(THD *thd, TABLE_LIST *tables, COND *cond);

参数描述:

参数名

类型

描述

thd

THD*

当前执行query的线程

tables

TABLE_LIST

当前query所指定的表(在i_s中的表,都是查询时才自动创建的临时表),我们需要把数据放如到tables中

cond

COND*

where条件,我们可以在函数中通过cond来过滤数据,只把需要的记录加入到临时表中。也可以不搭理,让mysql层来处理

 

为了将记录保存到i_s表中,这里不的不提到两个函数:field类的成员函数store_系列函数和schema_table_store_record(),前者用来存储数据到表结构体追踪,后者用来将一行存储好的数据放入到临时表中。

store函数是Field类的方法,有5个:

函数

描述

Field::store(const char *to, uint length, CHARSET_INFO *cs)

to:字符串指针;length:字符串长度

cs:字符串的字符集,默认的字符集为system_charset_info

bin类型为my_charset_bin、

latin1类型为 my_charset_latin1,此外,我们还可以通过get_charset()、get_charset_by_name()或者get_charset_by_csname()来获得字符集信息

Field::store(longlong nr, bool unsigned_val)

nr:longlong整数值

unsigned_val:是否为unsigned类型

Field::store(double nr)

存储double类型

Field::store_decimal(const my_decimal *d)

decimal类型

Field::store_time(MYSQL_TIME *ltime, timestamp_type t_type)

时间类型

 

其中my_declimal类型或者MYSQL_TIME等MySQL代码内特有的类型,我们都可以通过引入相应的代码来构建结构体。

注意当列被声明为MY_I_S_MAYBE_NULL时,需要做一些额外的处理,见之前关于st_field_info结构体的介绍。

当store数据到Field结构体后,我们还需要将其存储到表中,API函数如下:

boolschema_table_store_record(THD *thd, TABLE *table);

其中thd为当前线程,table为tables->table

 

为了包含这两个函数,我们需要引入如下头文件:

#include

 

4)使用COND进行优化

从fill_table的函数原型中,我们可以看到结构体COND,这在MySQL层用作对条件进行记录过滤,实际上在plugin里,我们可以直接进行过滤,只返回到MYSQL层需要的数据。如下图所示:

 

如果你接触过源代码,会发现COND是一个相当复杂的类型,如果由我们自己编写代码来操作显然要耗费大量的精力,我们可以依葫芦画瓢,找到源代码里是如何使用该结构体的,构造相应的参数,就可以直接调用了,这也是Plugin的诱人之处,我们可以根据需求引用已有的代码。

MySQL层代码大多定义在sql文件夹下,我们在编译时指定相应的目录即可。

当我们的操作对系统影响比较大时,需要尽快的得到结果,例如,内建的I_S表COLUMNS,在填充数据时需要打开所有的表,如果在Plugin层做过滤,那么当我们找到一个不符合条件的表时,尽快关闭,而不是等到MYSQL层来过滤后关闭。

例如函数:

bool calc_lookup_values_from_cond(THD *thd,COND *cond, TABLE_LIST *table, LOOKUP_FIELD_VALUES *lookups);

其中LOOPUP_FIEDL_VALUES结构体为:

sql/sql_show.cc:

typedef struct st_lookup_field_values

{

LEX_STRING value1, value2;

bool value1_is_wildcard, value2_is_wildcard;

} LOOKUP_FIELD_VALUES;

 

这个函数用于处理等值的情况,函数将寻找类似field1 = constant1 和field2 = constant2这样的条件, 如果找到了,将被存储在LOOKUP_FIELD_VALUES结构体的value1和value2中:

lookups.value1.str

lookups.value2.str

当我们找到了在COND中定义的条件后,就可以进行字符串匹配了。

该函数用于支持INFORMATION_SCHEMA.TABLES, INFORMATION_ SCHEMA.COLUMNS,和其他类型的内建I_S表,主要用来存储表名和数据库名,也就是说,value值为string类型,并且只支持两个等值操作,如果想实现更复杂的cond遍历,我们需要自己来实现。

 

示例如下(参考自《mysql plugin development》):

 

 

 

 

 

view plain

#include  

  

/*声明相关的结构体和函数*/ 

typedef struct st_lookup_field_values 

LEX_STRING value1, value2; 

bool value1_is_wildcard,value2_is_wildcard; 

} LOOKUP_FIELD_VALUES; 

bool calc_lookup_values_from_cond(THD *thd,COND *cond, 

TABLE_LIST *table, LOOKUP_FIELD_VALUES*lookups); 

bool schema_table_store_record(THD *thd,TABLE *table); 

  

/*定义列类型

*包括一个整型和一个字符串型

*/ 

ST_FIELD_INFO cond_push_fields[] = 

{"NUMBER",10, MYSQL_TYPE_LONG, 0, 0, 0, 0}, 

{"TEXT",100, MYSQL_TYPE_STRING, 0, 0, 0, 0}, 

{0, 0,MYSQL_TYPE_NULL, 0, 0, 0, 0} 

  

int fill_cond_push(THD *thd, TABLE_LIST*tables, COND *cond) 

         /*系统默认字符集:utf-8*/ 

CHARSET_INFO *cs= system_charset_info; 

TABLE *table =tables->table; 

  

/*字符串数组output,用于测试只返回符合条件的字符串*/ 

const char**ptr, *output[] = {"hello", "world", "this", "is","a", "test", 0}; 

int num; 

  

/*声明变量*/ 

LOOKUP_FIELD_VALUESlookups; 

bzero((char*)&lookups, sizeof(lookups)); 

/*调用函数获得COND中定义的条件*/ 

if (calc_lookup_values_from_cond(thd, cond, tables,&lookups)) 

return 0; 

for (num = 0,ptr = output; *ptr; ptr++) 

if (lookups.value1.str && 

my_strnncoll(cs, (const uchar*)*ptr, strlen(*ptr), 

(const uchar*)lookups.value1.str, 

lookups.value1.length)) 

continue; 

                   

                   /*只有满足条件的字符串才会被存储到table中*/ 

table->field[0]->store(++num); 

table->field[1]->store(*ptr, strlen(*ptr), cs); 

if (schema_table_store_record(thd, table)) 

return 1; 

return 0; 

/*初始化i_s plugin*/ 

int cond_push_init(void *p) 

ST_SCHEMA_TABLE*schema = (ST_SCHEMA_TABLE*) p; 

/*指定表定义*/ 

schema->fields_info= cond_push_fields; 

/*指定记录填充函数*/ 

schema->fill_table= fill_cond_push; 

schema->idx_field1= 1; 

return 0; 

struct st_mysql_information_schemacond_push= 

{MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; 

  

mysql_declare_plugin(cond_push) 

MYSQL_INFORMATION_SCHEMA_PLUGIN, 

&cond_push, 

"COND_PUSH", 

"AndrewHutchings (Andrew.Hutchings@Sun.COM)", 

"A simplecondition pushdown demo table", 

PLUGIN_LICENSE_GPL, 

cond_push_init, 

NULL, 

0x0010, 

NULL, 

NULL, 

NULL 

mysql_declare_plugin_end; 

 

 

5)例子:获取当前query cache中的QUERY信息(摘自网络,略改)

Query_cache中的query 存储在query_cache->queries结构体中,这是一个hash表,我们可以遍历其中的记录还获得想要的数据,代码如下:

 

view plain

#include  

#include  

/*内核中一些代码定义在MYSQL_SERVER宏中*/ 

#ifndef MYSQL_SERVER 

#define MYSQL_SERVER 

#endif 

  

/*sql_cache.cc中包含了全部跟querycache相关的代码*/ 

#include  

#include  

#include  

#include  

#include  

#include  

  

/*创建一个子类,query_cache的成员queries为私有变量

class Accessible_Query_Cache : privateQuery_cache {

public:

 HASH *get_queries()

  {

   return &this->queries; //&query_cache.queries;

  }

};

 

bool schema_table_store_record(THD *thd,TABLE *table);

 

#define MAX_STATEMENT_TEXT_LENGTH 32767

#define COLUMN_STATEMENT_ID 0

#define COLUMN_SCHEMA_NAME 1

#define COLUMN_STATEMENT_TEXT 2

#define COLUMN_RESULT_BLOCKS_COUNT 3

#define COLUMN_RESULT_BLOCKS_SIZE 4

#define COLUMN_RESULT_BLOCKS_SIZE_USED 5

 

/* 定义表结构*/ 

ST_FIELD_INFOmysql_is_cached_queries_fields[]= 

 {"STATEMENT_ID", 21, MYSQL_TYPE_LONG, 0, 0, "Id"}, 

 {"SCHEMA_NAME", 64, MYSQL_TYPE_STRING, 0, 0,"Schema"}, 

 {"STATEMENT_TEXT", MAX_STATEMENT_TEXT_LENGTH,MYSQL_TYPE_STRING, 0, 0, "Statment text"}, 

 {"RESULT_BLOCKS_COUNT", 21, MYSQL_TYPE_LONG, 0, 0, "CountResult Blocks"}, 

 {"RESULT_BLOCKS_SIZE", 21, MYSQL_TYPE_LONGLONG, 0, 0,"Size Result Blocks"}, 

 {"RESULT_BLOCKS_SIZE_USED", 21, MYSQL_TYPE_LONGLONG, 0, 0,"Size Used Result Blocks"}, 

  {0,0, MYSQL_TYPE_STRING, 0, 0, 0} 

}; 

  

/*填充数据函数*/ 

static intmysql_is_cached_queries_fill_table(THD *thd, TABLE_LIST *tables, COND *cond) 

  intstatus;                                

 CHARSET_INFO *scs= system_charset_info;  /* need this to store field into table */ 

 TABLE *table= (TABLE *)tables->table;     

 Accessible_Query_Cache *qc; 

 HASH *queries; 

 const uchar *query_cache_block_raw; 

 Query_cache_block* query_cache_block; 

 Query_cache_query* query_cache_query; 

  

 uint result_blocks_count; 

 ulonglong result_blocks_size; 

 ulonglong result_blocks_size_used; 

 Query_cache_block *first_result_block; 

 Query_cache_block *result_block; 

  

 const char *statement_text; 

 size_t statement_text_length; 

  

 const char *key; 

 size_t key_length; 

  

  

/*引用query_cache全局变量*/ 

  qc= (Accessible_Query_Cache *)&query_cache; 

  

/*对query_cache加锁*/ 

  query_cache.lock(); 

/*获取hash*/ 

 queries = qc->get_queries(); 

  

  /* 遍历hash中的所有记录/

  for(uint i= 0; i records; i++)

  {

         /*根据索引号获取记录*/ 

   query_cache_block_raw = hash_element(queries, i); 

   query_cache_block = (Query_cache_block*)query_cache_block_raw; 

query_cache_query= query_cache_block->query(); 

  

   table->field[COLUMN_STATEMENT_ID]->store(i+1, 0); 

  

   /* 获取sql语句*/ 

   statement_text = (const char*)query_cache_query->query(); 

   statement_text_length = strlen(statement_text); 

    

/*当超出长度时需要截断…*/ 

   table->field[COLUMN_STATEMENT_TEXT]->store(  (char*)statement_text 

                                ,statement_text_length > MAX_STATEMENT_TEXT_LENGTH? 

MAX_STATEMENT_TEXT_LENGTH 

                                :statement_text_length 

                                , scs 

   ); 

    

   /* 获取该查询的key*/ 

   key = (const char*)query_cache_query_get_key(  query_cache_block_raw                          

                                                           ,&key_length  , 0 ); 

    

key_length =strlen(key+statement_text_length+1)-1; 

    

         /*数据库名是key的一部分,适当的偏移key指针可以得到数据库名*/ 

   table->field[COLUMN_SCHEMA_NAME]->store((char*)key+statement_text_length+1 

                                             , key_length 

                                             ,scs  ); 

    

   /*获得结果集所占块的个数*/ 

   first_result_block= query_cache_query->result(); 

   if(first_result_block) 

    { 

     /* initialize so we can loop over the result blocks*/ 

     result_block= first_result_block; 

     result_blocks_count = 1;     

     result_blocks_size = result_block->length; 

     result_blocks_size_used = result_block->used; 

      

     /* loop over the result blocks*/ 

     while((result_block= result_block->next)!=first_result_block) 

     { 

       /* calculate total number of result blocks */ 

             result_blocks_count++;               

       /* calculate total size of result blocks */ 

       result_blocks_size += result_block->length; 

       /* calculate total of used size of result blocks */ 

       result_blocks_size_used += result_block->used; 

     } 

    } 

   else 

    { 

     result_blocks_count = 0; 

     result_blocks_size = 0; 

     result_blocks_size_used = 0; 

    } 

   /* 存储块的个数*/ 

   table->field[COLUMN_RESULT_BLOCKS_COUNT]->store( result_blocks_count ,0); 

   /* 存储总的所占有块的大小*/ 

   table->field[COLUMN_RESULT_BLOCKS_SIZE]->store( result_blocks_size  , 0); 

   /*存储总的已使用块的大小*/ 

   table->field[COLUMN_RESULT_BLOCKS_SIZE_USED]->store(result_blocks_size_used , 0 ); 

    

   /* 将记录存储到表中*/ 

   status = schema_table_store_record(thd, table); 

   if (status) {                                               

     status= 1;                                    

           goto cleanup;                    

  } 

 status = 0;                                      

  

cleanup:                                          

  query_cache.unlock(); 

 return status; 

  

static intmysql_is_cached_queries_plugin_init(void *p) 

 ST_SCHEMA_TABLE *schema= (ST_SCHEMA_TABLE *)p; 

  

 schema->fields_info= mysql_is_cached_queries_fields; 

 schema->fill_table= mysql_is_cached_queries_fill_table; 

  

 return 0; 

  

  

static int mysql_is_cached_queries_plugin_deinit(void*p) 

         return0; 

  

struct st_mysql_information_schemamysql_is_cached_queries_plugin= 

{MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; 

  

/*

 Plugin library descriptor

*/ 

  

mysql_declare_plugin(mysql_is_cached_queries) 

 MYSQL_INFORMATION_SCHEMA_PLUGIN, 

 &mysql_is_cached_queries_plugin, 

 "MYSQL_CACHED_QUERIES", 

 "Roland Bouman", 

 "Lists all queries in the query cache.", 

 PLUGIN_LICENSE_GPL, 

 mysql_is_cached_queries_plugin_init, /* Plugin Init */ 

 mysql_is_cached_queries_plugin_deinit, /* Plugin Deinit */ 

 0x0010 /* 1.0 */, 

 NULL,                       /*status variables                */ 

 NULL,                       /*system variables                */ 

 NULL                        /*config options                  */ 

mysql_declare_plugin_end; 

view plain

 <p>	view plain</p><p>	参考  </p><p>	view plain</p><p>	1.《MySQL Plugin Development》 </p><p>	view plain</p><p>	2. MySQL5.1.48源代码 </p><p>	view plain</p>						bitsCN.com
    
Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn