MySQL:如何編寫Information Schema Plugin

轉載請署名:印風

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*

僅用於內建的i_s

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 <mysql_priv.h>

 

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》):

#include <mysql_priv.h>
 
/*聲明相關的結構體和函數*/
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 ([email protected])",
"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表,我們可以遍歷其中的記錄還獲得想要的數據,代碼如下:

#include <stdlib.h>
#include <ctype.h>
/*內核中一些代碼定義在MYSQL_SERVER宏中*/
#ifndef MYSQL_SERVER
#define MYSQL_SERVER
#endif
 
/*sql_cache.cc中包含了全部跟querycache相關的代碼*/
#include <sql_cache.cc>
#include <mysql_priv.h>
#include <mysql/plugin.h>
#include <my_global.h>
#include <mysql_version.h>
#include <my_dir.h>
 
/*創建一個子類,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 < queries->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;
參考 
1.《MySQL Plugin Development》
2. MySQL5.1.48源代碼

發佈了124 篇原創文章 · 獲贊 11 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章