- 编译&链接
gcc -c -I/usr/local/include/mysql myclient.c
gcc -o myclient myclient.o -L/usr/local/lib/mysql -lmysqlclient
使用mysql_config工具.
mysql_config --include// 显示需用-I指出的路径信息
mysql_config --libs// 显示需用-L和-l指出的开发库和链接库信息
在编译命令中使用上述.可把上述放入反引号.
系统的shell程序会执行mysql_config,并用其输出替换反引号里的命令.
CC= gcc
INCLUDES= - I/ usr/ local/ include/ mysql
LIBS= - L/ usr/ local/ lib/ mysql - lmysqlclient
all : myclient
main. o: main. c myclient. h
$( CC) - c $( INCLUDES) main. c
lib. o: lib. c myclient. h
$( CC) - c $( INCLUDES) lib. c
myclient: main. o lib. o
$( CC) - o myclient main. o lib. o $( LIBS)
clean:
rm - f myclient main. o lib. o
INCLUDES=${shell mysql_config --include}
LIBS=${shell mysql_config --libs}
当make运行时,会执行每一个mysql_config命令,
并用其输出来设置相应的变量值.
其中的${shell}结构是受GNU make支持的.
如你的make版本不是基于GNU make构建的,
则可能需要使用一种稍有差异的语法.
出错处理和命令行选项处理
- 出错检查
1.mysql_error()
会返回一个包含出错消息的字符串
2.mysql_errno()
会返回一个MySQL特有的数字出错代码
3.mysql_sqlstate()
会返回一个SQLSTATE代码,
SQLSTATE值与数据库软件的提供商无关,它依据的是ANSI SQL和ODBC标准.
- 处理SQL语句
1.构造语句
实现的方式取决于语句本身的内容
即它是否包含二进制数据
2.把语句发给服务器执行
服务器将执行这条语句,生成结果
3.对结果处理
MySQL客户端开发库包含两组用于执行的例程
第一组例程,
会把每条语句当做一个字符串发给服务器
且会按字符串格式返回所有数据列的结果
第二组例程,
使用的是二进制协议,
能以原始格式发送和返回非字符串数据,不需来回转换字符串格式.
常用的语句调用例程是mysql_read_query()
借助此例程,可把语句当做一个计数字符串[字符串+长度值]
另一语句调用函数是mysql_query()
它对语句字符串里所允许的内容有更多的限制,更易于使用.
传给mysql_query()的语句,需是一个以null结尾的字符串.
if ( mysql_query ( conn, stmt_str) != 0 )
{
}
else
{
}
所有语句可粗略划分为两大类
一类用于修改行,
一类会返回一个结果集
结果集,会通过MYSQL_RES数据类型来表示.
该数据类型其实是一个结构,
里面包含了各个行的数据值,及关于这些值的元数据.
- 处理那些修改行的语句
要处理修改行的语句,
可用mysql_query()/mysql_real_query()来调用它.
如成功,
则调用mysql_affected_rows()可查明实际插入,删除或修改了多少行.
if ( mysql_query ( conn,
"INSERT INTO my_tbl SET name='My Name'" ) != 0 )
{
print_error ( conn, "INSERT statement failed" ) ;
}
else
{
printf ( "INSERT statement succeeded;\
number of rows affected:%lu\n" ,
( unsigned long ) mysql_affected_rows ( conn) ) ;
}
- 处理那些返回结果集的语句
有些语句会返回一个结果集
调用这类语句时,
如用了mysql_query()/mysql_real_query()
则检索出来的数据将以结果集的形式返回.
MYSQL里能返回行的语句并非只有SELECT
SHOW,DESCRIBE,EXPLAIN和CHECK TABLE这样的语句
也都返回结果集
对这些语句,调用后,需执行附加的行处理操作.
1.调
mysql_store_result()
或mysql_use_result()可生成结果集
这两个函数调用成功时,
会返回一个MYSQL_RES指针
失败时,
会返回NULL
mysql_store_result()会立刻从服务器检索出行,
把它们缓存在客户端的内存里
2.调mysql_fetch_row()可依次取回结果集里的每个行
此函数调用成功时,
会返回一个MYSQL_ROW值.
如已没有行,
会返回NULL
返回的MYSQL_ROW值其实是一个字符串数组指针,
其中的字符串分别代表着这个行里各个列的值.
3.处理完结果集后,
需调mysql_free_result()释放它所使用的内存资源.
MYSQL_RES * res_set;
if ( mysql_query ( conn, "SHOW TABLES FROM sampdb" ) != 0 )
{
print_error ( conn, "mysql_query() failed" ) ;
}
else
{
res_set = mysql_store_result ( conn) ;
if ( res_set == NULL )
{
print_error ( conn,
"mysql_store_result() failed" ) ;
}
else
{
process_result_set ( conn, res_set) ;
mysql_free_result ( res_set) ;
}
}
MYSQL_ROW row;
while ( ( row= mysql_fetch_row ( res_set) ) != NULL )
{
}
mysql_fetch_row将返回一个MYSQL_ROW值,它是一个数组指针.
如把这个返回值赋值给一个名为row的变量
则可使用row[i]语法来访问行中的各个值
i的范围是从0到行里的列数减少1
关于MYSQL_ROW有以下几个值得注意地方
a.MYSQL_ROW是指针类型
应定义为MYSQL_ROW row;
b.在MYSQL_ROW数组里,
对所有类型的值,甚至包括数字类型的,
都是以字符串方式返回的.
如想把某个值当作数字,
则需自己对字符串进行转换
c.MYSQL_ROW数组里的字符串都是以null结尾的.
如某列可包含二进制数据,则它可能包含null字节.
因此不能把这个值当作是以null结尾的字符串来对待.
可通过考虑数据列长度来表达数据长度
d.在MYSQL_ROW数组里,
SQL的NULL将被表示成C语言的NULL指针.
如何处理各个行,取决于应用的具体目标
void process_result_set (
MYSQL* conn,
MYSQL_RES* res_set)
{
MYSQL_ROW row;
unsigned int i;
while ( ( row = mysql_fetch_row ( res_set) ) != NULL )
{
for ( i = 0 ; i < mysql_num_fields ( res_set) ; i++ )
{
if ( i > 0 )
{
fputc ( '\t' , stdout ) ;
}
printf ( "%s" , row[ i] != NULL ? row[ i] : "NULL" ) ;
}
fputc ( '\n' , stdout ) ;
}
if ( mysql_errno ( conn) != 0 )
{
print_error ( conn, "mysql_fetch_row() failed" ) ;
}
else
{
printf ( "Number of rows returned:%lu\n" ,
( unsigned long ) mysql_num_rows ( res_set) ) ;
}
}
为了让输出变得更美观点
可提供一些信息,如列标签
让这些值都垂直对齐.
这些信息可获得,但位于结果集的元数据里.
- 通用的语句处理器
可正确地处理各种SQL语句
不管它是否会返回结果集,不管它是否会执行成功,都能正确处理.
实现过程:
1.执行语句,如失败,则结束.
2.如执行成功,则调mysql_store_result()从服务器检索行,
并创建一个结果集.
3.如mysql_store_result()函数调用成功,
则语句返回一个结果集
调mysql_fetch_row()来处理各个行
一直到它返回NULL为止
然后释放此结果集
4.如mysql_store_result()调用失败,
具体原因可能是:
语句根本不会返回结果集,
也可能是它需要返回结果集,但试图创建结果集时发生了错误.
可利用mysql_field_count()来加以区别,
即把连接处理器传递给mysql_field_count()
然后检查它的返回值
如mysql_field_count()返回0
则表明此语句没返回任何列
故,也就没有结果集.
如mysql_field_count()返回是非0值,
则表明有错误.
因为该语句应返回一个结果集,但却未返回.
下述函数能处理任何语句,
前提为给定一个连接处理器和一个以null结尾的语句字符串
void process_statement (
MYSQL* conn,
char * stmt_str)
{
MYSQL_RES * res_set;
if ( mysql_query ( conn, stmt_str) != 0 )
{
print_error ( conn, "Could not execute statement" ) ;
return ;
}
res_set = mysql_store_result ( conn) ;
if ( res_set)
{
process_result_set ( conn, res_set) ;
mysql_free_result ( res_set) ;
}
else
{
if ( mysql_field_count ( conn) == 0 )
{
printf ( "Number of rows affected:%lu\n" ,
( unsigned long ) mysql_affected_rows ( conn) ) ;
}
else
{
print_error ( conn, "Could not retrieve result set" ) ;
}
}
}
- 另一种语句处理处理方法
上述的process_statement()有以下3个特点
1.使用了mysql_query()来执行语句
2.使用了mysql_store_result()来检索结果集
3.没获得结果集时,
利用mysql_field_count()来判断:
是出现了错误,还是原本就不会有结果集返回.
如对语句处理的这3个方面加以调整,就能得到另一种处理方案.
1.执行语句时,
用一个计数字符串和mysql_real_query()来代替那个以null结尾的字符串和mysql_query()
2.通过调mysql_use_result()
而非调mysql_store_result()来创建结果集
3.调mysql_error()或mysql_errno()
而非mysql_field_count()来确定是结果集检索失败,
还是根本就没有供检索的结果集
void process_real_statement (
MYSQL* conn,
char * stmt_str,
unsigned int len)
{
MYSQL_RES* res_set;
if ( mysql_real_query ( conn, stmt_str, len) != 0 )
{
print_error ( conn, "Could not execute statement" ) ;
return ;
}
res_set = mysql_use_result ( conn) ;
if ( res_set)
{
process_result_set ( conn, res_set) ;
mysql_free_result ( res_set) ;
}
else
{
if ( mysql_errno ( conn) == 0 )
{
printf ( "Number of rows affected:%lu\n" ,
( unsigned long ) mysql_affected_rows ( conn) ) ;
}
else
{
print_error ( conn, "Could not retrieve result set" ) ;
}
}
}
- mysql_store_result()与mysql_use_result()的对比
两者相似点:
1.都会接受一个连接处理器参数,并返回一个结果集.
差异:
最本质的差异是,它们从服务器检索结果集的行的方式不同
当你调用mysql_store_result()时
它会立刻检索出所有的行
而mysql_use_result()则只会对检索进行初始化,
并不会实际取回任何行.
当从服务器检索结果集时,
mysql_store_result()会取回行
并为它们分配内存
然后把它们在客户端缓存起来.
此后的mysql_fetch_row()调用决不会返回出错消息[它们只是从已经含结果集的数据结构里提取行]
当mysql_fetch_row()返回NULL值时,肯定表示已经到达结果集的末尾.
mysql_use_result()本身不会检索任何行.
它只是逐行完成对检索的初始化工作
你需自己针对每一个行调mysql_fetch_row()来完成检索操作.
当mysql_fetch_row()返回NULL时,
通常表示"已经到达结果集的末尾"
也有可能表示"与服务器的通信出现了错误"
通过调mysql_error()或mysql_errno()可区分这两种情况.
mysql_store_result()需把完整的结果集保存在客户端
它的内存和处理需求比mysql_use_result()大.
内存分配和数据结构创建所需的开销会更大,
且如客户端检索的是大型结果集,
有内存耗尽的风险.
如要检索包含大量行的结果集,应使用mysql_use_result()
mysql_use_result()每次只会取回一个行进行处理
所以它对内存的要求低
同时,不必为创建结果集而建立各种复杂的数据结构
故,其内存分配速度也更快
另一方面,
mysql_use_result()加重了服务器的负担,
服务器需保存结果集里的行
一直到客户端程序认为适合检索所有这些行那一刻为止
故mysql_use_result()不适合用在以下两类客户端程序里:
1.根据用户请求,
逐个遍历各有关行的交互式客户端程序
2.两次行检索操作间,
需执行大量处理操作的客户端程序
虽然mysql_store_result()会消耗较多的内存
但也有好处
可立即访问整个结果集
结果集的所有行都可用,可随机访问.
mysql_data_seek()
mysql_row_seek()
mysql_row_tell()
可让你按任意顺序访问各个行可让你按任意顺序访问各个行.
如用mysql_use_result(),只能按mysql_fetch_row()取回行时的顺序访问各个行.
使用mysql_store_result()可访问某些使用my_use_result()时访问不到的列信息.
调mysql_num_rows()可获得结果集里行的数量.
每一列的值的最大宽度都存储在MYSQL_FIELD列信息结构的max_width成员里.
如用mysql_use_result(),则mysql_num_rows()会等你取回所有行之后,
才返回正确的值.
max_width的值也只在行全部取回后,才正确地计算出来,
用mysql_use_result()时,
客户端需通过mysql_fetch_row()调用取回结果集里的每个行.
要避免此问题,需在执行第二条语句前,调mysql_free_result()取回和丢弃尚未被取回的行.
mysql_store_result()不会导致数据不同步问题,
它返回时,一次取完相关数据.
- 使用结果集元数据
结果集不仅包含从数据行里检索出来的列值,
还包含与这些数据有关的信息.
这种信息即为结果集"元数据"
1.结果集里的行数和列数
调mysql_num_rows()和mysql_num_fields()即可获得
2.当前行里各列值的长度
调mysql_fetch_lengths()即可获得
3.关于各列的信息
如列的名字,类型,每个列的值最大宽度,包含这个列的表
上述信息存于MYSQL_FIELD结构里.
调mysql_fetch_field()即可获得.
元数据是否可用,取决于所用的结果集处理方法
如想用行计数或各个列的最大长度值,
则在创建结果集时,
需使用mysql_store_result()
不能用mysql_use_result()
结果集元数据能帮我们决定如何处理结果集数据
1.列的名字及宽度有助于生成漂亮格式的输出
2.列计数则表明
依次处理各个行中各个列的循环,
要迭代多少次才结束
3.行和列的计数
有助于为那些依赖于结果集维度的数据结构分配内存
4.列的数据类型
输出结果格式化:
1.确定各列的显示宽度
2.输出一行框起来的列标签
3.输出结果集每一个行里的各个值
把每个列都框起来,垂直对齐
对数字,按右对齐输出
对NULL,输出"NULL"
4.输出检索到的行总数
MYSQL_FIELD * field;
unsigned long col_len;
unsigned int i;
mysql_field_seek ( res_set, 0 ) ;
for ( i= 0 ; i < mysql_num_fields ( res_set) ; i++ )
{
field = mysql_fetch_field ( res_set) ;
col_len = strlen ( field-> name) ;
if ( col_len < field-> max_length)
{
col_len = field-> max_length;
}
if ( col_len < 4 && ! IS_NOT_NULL ( field-> flags) )
{
col_len = 4 ;
}
field-> max_length = col_len;
}
void print_dashes ( MYSQL_RES* res_set)
{
MYSQL_FIELD * field;
unsigned int i, j;
mysql_field_seek ( res_set, 0 ) ;
fputc ( '+' , stdout ) ;
for ( i= 0 ; i< mysql_num_fields ( res_set) ; i++ )
{
field = mysql_fetch_field ( res_set) ;
for ( j= 0 ; j < field-> max_length+ 2 ; j++ )
{
fputc ( '-' , stdout ) ;
}
fputc ( '+' , stdout ) ;
}
fputc ( '\n' , stdout ) ;
}
void process_result_set (
MYSQL* conn,
MYSQL_RES* res_set)
{
MYSQL_ROW row;
MYSQL_FIELD * field;
unsigned long col_len;
unsigned int i;
mysql_field_seek ( res_set, 0 ) ;
for ( i= 0 ; i< mysql_num_fields ( res_set) ; i++ )
{
field= mysql_fetch_field ( res_set) ;
col_len= strlen ( field-> name) ;
if ( col_len < field-> max_length)
{
col_len = field-> max_length;
}
if ( col_len < 4 && ! IS_NOT_NULL ( field-> flags) )
{
col_len = 4 ;
}
field-> max_length = col_len;
}
print_dashes ( res_set) ;
fputc ( '|' , stdout ) ;
mysql_field_seek ( res_set, 0 ) ;
for ( i= 0 ; i < mysql_num_fields ( res_set) ; i++ )
{
field = mysql_fetch_field ( res_set) ;
printf ( " %-*s |" , ( int ) field-> max_length, field-> name) ;
}
fputc ( '\n' , stdout ) ;
print_dashes ( res_set) ;
while ( ( row= mysql_fetch_row ( res_set) ) != NULL )
{
mysql_field_seek ( res_set, 0 ) ;
fputc ( '|' , stdout ) ;
for ( i= 0 ; i< mysql_num_fields ( res_set) ; i++ )
{
field= mysql_fetch_field ( res_set) ;
if ( row[ i] == NULL )
{
printf ( " %-*s |" , ( int ) field-> max_length, "NULL" ) ;
}
else if ( IS_NUM ( field-> type) )
{
printf ( " %*s |" , ( int ) field-> max_length, row[ i] ) ;
}
else
{
printf ( " %-*s |" , ( int ) field-> max_length, row[ i] ) ;
}
}
fputc ( '\n' , stdout ) ;
}
print_dashes ( res_set) ;
printf ( "Number of rows returned:%lu\n" ,
( unsigned long ) mysql_num_rows ( res_set) ) ;
}
- 对特殊字符和二进制数据进行编码
如在某个用引号引起来的字符串里,包含一个引号字符,
则可采用双写这个引号,或在其前面加一个反斜线的方式来解决
1.处理包含特殊字符的字符串
如在语句里单纯以字面方式插入包含有引号,null字节或反斜线的数据值
则在执行此语句时会引起各种问题.
如,
你想基于以null结尾的字符串的内容[指向它的是一个名为name_val的变量],构造SELECT语句
char stmt_buf[ 1024 ] ;
sprintf (
stmt_buf,
"SELECT * FROM mytbl\
WHERE name='%s'" , name_val) ;
如name_val含引号,最终构造出的语句将非法
这类问题可用mysql_real_escape_string()来解决
此函数会对特殊字符进行编码
让它们在用引号引起来的字符串里变得可用
mysql_real_escape_string()会把空字节,单引号,双引号,反斜线,换行符,回车符,Ctrl+Z当作特殊字符.
应在什么时候用mysql_real_escape_string()?
最好一直都用.
mysql_real_escape_string()会对有问题的字符进行编码
它会把它们转换成两个以反斜线开头的字符序列
如,
null字节会被编码成"\0"
其中的"0"是可输出的ASCII字符0,不是NULL
反斜线,单引号,双号将分别被编码成"\\", "\'", "\""
to_len= mysql_real_escape_string (
conn,
to_str,
from_str,
from_len) ;
mysql_real_escape_string()会对from_str进行编码,
把结果写入to_str
还会在结尾添加null
from_str指向的是一个char缓冲区,其中含要被编码的字符串
这个字符串可包含任何内容,
包括二进制数据
to_str指向的是一个已经存在的char缓冲区,
其中写入的是编码后的字符串
不要传递未经初始化的指针或NULL指针,
to_str指向的那个缓冲区的长度,
必须至少为(from_len*2)+1个字节
from_str允许包含null字节,且不能把它当做是以null结尾的字符串.
to_len是mysql_real_escape_string()的返回值,
是编码结果字符串的实际长度,未把结尾的null计算在内.
mysql_real_escape_string()返回时,
from_str里的null字节都会被编码成可打印的"\0"序列
故to_str里的编码结果可被当作是以null字节结尾的字符串
char stmt_buf[ 1024 ] , * p;
p= strcpy (
stmt_buf,
"SELECT * FROM mytbl\
WHERE name='" ) ;
p+ = strlen ( p) ;
p+ = mysql_real_escape_string (
conn,
p,
name_val,
strlen ( name_val) ) ;
* p++ = '\'' ;
* p= '\0' ;
char stmt_buf[ 1024 ] , buf[ 1024 ] ;
( void ) mysql_real_escape_string (
conn,
buf,
name_val,
strlen ( name_val) ) ;
sprintf (
stmt_buf,
"SELECT * FROM mytbl\
WHERE name='%s'" , buf) ;
- 处理二进制数据
在语句里使用了任意的二进制数据
因为二进制值可包含任何字符,所以把它们直接嵌到语句里并不安全
设需从文件里读出图像
并把它们存入一个名为picture的表
同时还会存入一个唯一标识符
对长度小于16MB的二进制值来说,
MEDIUMBLOB是个好的选择
CREATE TABLE picture
{
pict_id INT NOT NULL PRIMARY KEY,
pict_data MEDIUMBLOB
} ;
int load_image ( MYSQL* conn, int id, FILE* f)
{
char stmt_buf[ 1024 * 1024 ] , buf[ 1024 * 10 ] , * p;
unsigned long from_len;
int status;
sprintf (
stmt_buf,
"INSERT INTO picture(pict_id,pict_data)\
VALUES (%d,'" , id) ;
p = stmt_buf + strlen ( stmt_buf) ;
while ( ( from_len= fread ( buf, 1 , sizeof ( buf) , f) ) > 0 )
{
if ( p+ ( 2 * from_len) + 3 > stmt_buf+ sizeof ( stmt_buf) )
{
print_error ( NULL , "image is too big" ) ;
return 1 ;
}
p + = mysql_real_escape_string ( conn, p, buf, from_len) ;
}
* p++ = '\'' ;
* p++ = ')' ;
status = mysql_real_query ( conn, stmt_buf, ( unsigned long ) ( p- stmt_buf) ) ;
return status;
}
从数据库取回图像值[或任意二进制值]时,
没把它存入数据库时那么多问题,
这个数据值以原始格式存放在MYSQL_ROW变量里,
其长度可调mysql_fetch_lengths()获得.
应将此值作为一个计数字符串处理.
交互式语句执行程序
交互式语句执行客户端exec_stmt
允许你输入语句,
使用通用语句处理器process_statement()执行
使用显示格式处理器process_result_set()显示结果.
exec_stmt允许的输入内容有以下限制:
1.每个输入行需包含一条完整的语句
2.语句不应以分号或\g作为结尾
3.能识别出的唯一非SQL命令是quit和\q
- 一次执行多条语句
MySQL客户端开发库支持一次执行多条语句
据此,可把一个由分号隔开的多条语句构成的字符串,
发给服务器,再依次检索各个结果集.
一次执行多条语句,默认是禁用的.
如你想用它时,
需明确告诉服务器
1.在连接服务器时,把CLIENT_NULTI_STATEMENTS选项添加到mysql_real_connect()的标志参数里.
opt_flags | = CLIENT_MULTI_STATEMENTS;
if ( mysql_real_connect (
conn,
opt_host_name,
opt_user_name,
opt_password,
opt_db_name,
opt_port_num,
opt_socket_name,
opt_flags) == NULL )
{
print_error ( conn, "mysql_real_connect() failed" ) ;
mysql_close ( conn) ;
exit ( 1 ) ;
}
为现有连接启动此功能
if ( mysql_set_server_option (
conn,
MYSQL_OPTION_MULTI_STATEMENTS_ON) != 0 )
{
print_error ( conn, "Could not enable multiple-statement execution" ) ;
}
在处理多个结果集时,
可使用下面这两个函数,对结果检索的当前状态进行基本的检查
1.mysql_more_results()
如还有更多的结果,返回非0值.
否则,返回0
2.mysql_next_result()
如还有更多的结果,返回一个状态值,
同时初始化下一个结果集的检索工作.
如还有更多的结果,则状态值为0
如没有,则返回-1
如发生了错误,则返回一个大于0的值
使用这些函数时,
可把结果检索代码放在某个循环里.
在用常规代码检索出一个结果后,
记得检查下是否还有结果需检索.
如有,再循环一次.
如没有,退出循环.
根据循环语句具体结构的不同,
可能不需调mysql_more_results()
你可根据mysql_next_result()的返回值来判断是否还有更多的结果
void process_multi_statement (
MYSQL* conn,
char * stmt_str)
{
MYSQL_RES* res_set;
int status;
int keep_going = 1 ;
if ( mysql_query ( conn, stmt_str) != 0 )
{
print_error (
conn,
"Could not execute statement(s)" ) ;
return ;
}
do
{
res_set = mysql_store_result ( conn) ;
if ( res_set)
{
process_result_set ( conn, res_set) ;
mysql_free_result ( res_set) ;
}
else
{
if ( mysql_field_count ( conn) == 0 )
{
printf (
"Number of rows affected:%lu\n" ,
( unsigned long ) mysql_affected_rows ( conn) ) ;
}
else
{
print_error ( conn, "Could not retrieve result set" ) ;
keep_going = 0 ;
}
}
status = mysql_next_result ( conn) ;
if ( status != 0 )
{
keep_going = 0 ;
if ( status > 0 )
{
print_error (
conn,
"Could not execute statement" ) ;
}
}
} while ( keep_going) ;
}
- 使用服务器端预处理语句
本章前面几节中,
用于处理SQL的代码是基于MySQL客户端开发库提供的函数编写的.
都是以字符串的形式来发送和检索所有信息.
本节讨论如何使用二进制的客户端/服务器协议
二进制协议支持服务器端预处理语句,
且可按本地格式来传输数据值.
并非所有语句都能被预处理.
预处理语句API适用于这样几种语句
CREATE TABLE
DELETE
DO
INSERT
REPLACE
SELECT
SET
UPDATE
和绝大多数SHOW变体
预处理机制所支持的语句种类还在不断增加.
为使用二进制协议,
需创建一个语句处理器.
有此处理器,
便可把语句发到服务器进行预处理.
服务器会分析语句,记住它,并把有关它的信息[客户端开发库存储在语句处理器里的]发送回去.
用于预处理的语句可被参数化,即包含问号(?)
以此来表示,当你将来执行该语句时所提供的数据值会出现在该处.
INSERT INTO score ( event_id, student_id, score) VALUES ( ? , ? , ? )
上述语句含3个"?"字符,
它们的作用是充当参数标记或占位符
将来可提供数据值绑定到这些占位符上.
执行它时,这些数据便会让语句变得完整
预处理语句的这种"参数传递"机制使它们可重复使用
只要把各有关"参数"替换成某些不同的值
便可反复多次执行同一条语句
1.服务器只需对语句分析一次
2.能降低网络开销
因为每次执行时只需发数据值
3.数据值在发送时无需转换成字符串形式,
可降低执行开销
如上面INSERT语句的3个列全是INT列,
每当用mysql_query()或mysql_real_query()执行类似的INSERT语句时,
只需把几个数据值转换为字符串,嵌入语句文本即可.
有了预处理语句接口后,可用二进制格式来发送数据值
4.不必对检索结果转换
在预处理语句的结果集里,
非字符串值都是以二进制格式返回的.
无需转换成字符串形式.
二进制协议缺点:
1.难用
传输和接收数据值时需更多准备工作
2.二进制协议不支持所有的语句
3.对交互式程序,
最好还是非二进制协议.
预处理语句,适合提升反复执行的语句分摊效率.
用预处理语句基本流程
1.调mysql_stmt_init()分配一个语句处理器
此函数会返回一个指向处理器的指针,供后续使用
2.调mysql_stmt_prepare(),把语句发送到服务器,
让其接受预处理,并与语句处理器相关联.
服务器会分析该语句的特征,如语句类型,包含的参数标记,在执行时是否会生成一个结果集等.
3.如该语句含占位符,
则在执行它之前,
需为每个占位符提供相应的数据.
即为每个参数创建一个MYSQL_BIND结构
每个结构表明一个参数的数据类型,值,是否为NULL.
再调mysql_stmt_bind_param()函数,
把结构与该语句绑定在一起
4.调mysql_stmt_execute(),执行该语句
5.如该语句只修改数据,
不生成结果集
则调mysql_stmt_affected_rows()来确定该语句影响的行数
6.如该语句生成结果集,则可调mysql_stmt_result_metadata()来获取关于该结果集的元数据.
为获得这些行,需再次用MYSQL_BIND结构.
这次用于接收从服务器返回的数据.
需为结果集里的每一个列创建一个MYSQL_BIND结构.
它们包含与每一个行的数据值有关的信息,
这些值是你期望从服务器接收到的.
先调mysql_stmt_bind_result()
把这些结构绑定到语句处理器上,
再反复调mysql_stmt_fetch(),
依次取回每一个行
取回每一个行后,便可访问当前行的各个列值.
调mysql_stmt_fetch()前,
可选择调mysql_stmt_store_result()
如调了,
则可从服务器里一次性取回全部结果集的行
并缓存在客户端的内存里
也可通过调mysql_stmt_num_rows()确定结果集里的行数
如结果集为空,则函数返回0
取回结果集后,记得调mysql_stmt_free_result()
7.如再次执行刚才语句,
可返回3,指定新的参数值
8.如想用这个处理器来预处理另一条语句
则返回2
9.用完语句处理器后,
需调mysql_stmt_close()来释放它.
void process_prepared_statements ( MYSQL* conn)
{
MYSQL_STMT* stmt;
char * use_stmt = "USE sampdb" ;
char * drop_stmt = "DROP TABLE IF EXISTS t" ;
char * create_stmt = "CREATE TABLE t\
(i INT, f FLOAT, c CHAR(24), dt DATETIME)" ;
if ( mysql_query ( conn, use_stmt) != 0
|| mysql_query ( conn, drop_stmt) != 0
|| mysql_query ( conn, create_stmt) != 0 )
{
print_error ( conn, "Could not set up test table" ) ;
return ;
}
stmt = mysql_stmt_init ( conn) ;
if ( stmt == NULL )
{
print_error ( conn, "Could not initialize statement handler" ) ;
return ;
}
insert_rows ( stmt) ;
select_rows ( stmt) ;
mysql_stmt_close ( stmt) ;
}
static void print_stmt_error (
MYSQL_STMT* stmt,
char * message)
{
fprintf ( stderr , "%s\n" , message) ;
if ( stmt != NULL )
{
fprintf ( stderr , "Error %u (%s):%s\n" ,
mysql_stmt_errno ( stmt) ,
mysql_stmt_sqlstate ( stmt) ,
mysql_stmt_error ( stmt) ) ;
}
}
static void insert_rows ( MYSQL_STMT* stmt)
{
char * stmt_str = "INSERT INTO t \
(i,f,c,dt)\
VALUES(?,?,?,?)" ;
MYSQL_BIND param[ 4 ] ;
int my_init;
float my_float;
char my_str[ 26 ] ;
MYSQL_TIME my_datetime;
unsigned long my_str_length;
time_t clock;
struct tm* cur_time;
int i;
printf ( "Inserting records...\n" ) ;
if ( mysql_stmt_prepare ( stmt, stmt_str, strlen ( stmt_str) ) != 0 )
{
print_stmt_error ( stmt, "Could not prepare INSERT statement" ) ;
return ;
}
memset ( ( void * ) param, 0 , sizeof ( param) ) ;
param[ 0 ] . buffer_type = MYSQL_TYPE_LONG;
param[ 0 ] . buffer = ( void * ) & my_int;
param[ 0 ] . is_unsigned = 0 ;
param[ 0 ] . is_null = 0 ;
param[ 1 ] . buffer_type = MYSQL_TYPE_FLOAT;
param[ 1 ] . buffer = ( void * ) & my_float;
param[ 1 ] . is_null = 0 ;
param[ 2 ] . buffer_type = MYSQL_TYPE_STRING;
param[ 2 ] . buffer = ( void * ) my_str;
param[ 2 ] . buffer_length = sizeof ( my_str) ;
param[ 2 ] . is_null = 0 ;
param[ 3 ] . buffer_type = MYSQL_TYPE_DATETIME;
param[ 3 ] . buffer = ( void * ) & my_datetime;
param[ 3 ] . is_null = 0 ;
if ( mysql_stmt_bind_param ( stmt, param) != 0 )
{
print_stmt_error ( stmt, "Could not bind parameters for INSERT" ) ;
return ;
}
for ( i = 1 ; i <= 5 ; i++ )
{
printf ( "Inserting record %d ...\n" , i) ;
( void ) time ( & clock) ;
my_int = i;
my_float = ( float ) i;
( void ) strcpy ( my_str, ctime ( & clock) ) ;
my_str[ 24 ] = '\0' ;
my_str_length = strlen ( my_str) ;
param[ 2 ] . length = & my_str_length;
cur_time = localtime ( & clock) ;
my_datetime. year = cur_time-> tm_year + 1900 ;
my_datetime. month = cur_time-> tm_mon + 1 ;
my_datetime. day = cur_time-> tm_mday;
my_datetime. hour = cur_time-> tm_hour;
my_datetime. minute = cur_time-> tm_min;
my_datetime. second = cur_time-> tm_sec;
my_datetime. second_part = 0 ;
my_datetime. neg = 0 ;
if ( mysql_stmt_execute ( stmt) != 0 )
{
print_stmt_error ( stmt, "Could not execute statement" ) ;
return ;
}
sleep ( 1 ) ;
}
}
占位符通常代表VALUES()里列出的数据值
或WHERE子句里的数据值
有些地方不能用占位符
1.标识符
2.可把占位符放在操作符任何一侧,但不允许同时用在两侧
static void select_rows ( MYSQL_STMT* stmt)
{
char * stmt_str = "SELECT i,f,c,dt FROM t" ;
MYSQL_BIND param[ 4 ] ;
int my_int;
float my_float;
char my_str[ 24 ] ;
unsigned long my_str_length;
MYSQL_TIME my_datetime;
my_bool is_null[ 4 ] ;
printf ( "Retrieving records...\n" ) ;
if ( mysql_stmt_prepare ( stmt, stmt_str, strlen ( stmt_str) ) != 0 )
{
print_stmt_error ( stmt, "Could not prepare SELECT statement" ) ;
return ;
}
if ( mysql_stmt_field_count ( stmt) != 4 )
{
print_stmt_error ( stmt, "Unexpected column count from SELECT" ) ;
return ;
}
memset ( ( void * ) param, 0 , sizeof ( param) ) ;
param[ 0 ] . buffer_type = MYSQL_TYPE_LONG;
param[ 0 ] . buffer = ( void * ) & my_int;
param[ 0 ] . is_unsigned = 0 ;
param[ 0 ] . is_null = & is_null[ 0 ] ;
param[ 1 ] . buffer_type = MYSQL_TYPE_FLOAT;
param[ 1 ] . buffer = ( void * ) & my_float;
param[ 1 ] . is_null = & is_null[ 1 ] ;
param[ 2 ] . buffer_type = MYSQL_TYPE_STRING;
param[ 2 ] . buffer = ( void * ) my_str;
param[ 2 ] . buffer_length = sizeof ( my_str) ;
param[ 2 ] . length = & my_str_length;
param[ 2 ] . is_null = & is_null[ 2 ] ;
param[ 3 ] . buffer_type = MYSQL_TYPE_DATETIME;
param[ 3 ] . buffer = ( void * ) & my_datetime;
param[ 3 ] . is_null = & is_null[ 3 ] ;
if ( mysql_stmt_bind_result ( stmt, param) != 0 )
{
print_stmt_error ( stmt, "Could not bind parameters for SELECT" ) ;
return ;
}
if ( mysql_stmt_execute ( stmt) != 0 )
{
print_stmt_error ( stmt, "Could not execute SELECT" ) ;
return ;
}
if ( mysql_stmt_store_result ( stmt) != 0 )
{
print_stmt_error ( stmt, "Could not buffer result set" ) ;
return ;
}
else
{
printf ( "Number of rows retrieved:%lu\n" ,
( unsigned long ) mysql_stmt_num_rows ( stmt) ) ;
}
while ( mysql_stmt_fetch ( stmt) == 0 )
{
printf ( "%d" , my_int) ;
printf ( "%.2f" , my_float) ;
printf ( "%*.*s " ,
( int ) my_str_length,
( int ) my_str_length,
my_str) ;
printf (
"%04d-%02d-%02d %02d:%02d:%02d\n" ,
my_datetime. year,
my_datetime. month,
my_datetime. day,
my_datetime. hour,
my_datetime. minute,
my_datetime. second) ;
}
mysql_stmt_free_result ( stmt) ;
}
使用预处理CALL支持
可用预处理CALL语句来调存储过程
可访问OUT和INOUT类型过程参数的返回值
预处理CALL语句不能产生多个结果集,且调用者不能访问返回的参数值
CREATE PROCEDURE grade_event_stats
( IN p_event_id INT, OUT p_min INT, OUT p_max INT)
BEGIN
SELECT student_id, score
FROM score
WHERE event_id = p_event_id
ORDER BY student_id;
SELECT MIN ( score) , MAX ( score)
FROM score
WHERE event_id = p_event_id
INTO p_min, p_max;
END;
>> > SET @p_min = NULL , @p_max = NULL ;
>> > CALL grade_event_stats ( 4 , @p_min, @p_max) ;
>> > SELECT @p_min, @p_max;
if ( mysql_get_server_version ( conn) < 50503 )
{
print_error ( NULL , "..." ) ;
mysql_close ( conn) ;
exit ( 1 ) ;
}
stmt = mysql_stmt_init ( conn) ;
if ( ! stmt)
{
print_error ( NULL , "Could not initialize statement handler" ) ;
}
else
{
if ( exec_prepared_call ( stmt) == 0 )
{
process_call_result ( conn, stmt) ;
}
mysql_stmt_close ( stmt) ;
}
static int exec_prepared_call ( MYSQL_STMT* stmt)
{
MYSQL_BIND params[ 3 ] ;
int int_date[ 3 ] ;
int i;
if ( mysql_stmt_prepare ( stmt, "CALL grade_event_stats(?,?,?)" , 31 ) )
{
print_stmt_error ( stmt, "Cannot prepare statement" ) ;
return 1 ;
}
memset ( params, 0 , sizeof ( params) ) ;
for ( i = 0 ; i < 3 ; ++ i)
{
params[ i] . buffer_type = MYSQL_TYPE_LONG;
params[ i] . buffer = ( char * ) & int_data[ i] ;
params[ i] . length = 0 ;
params[ i] . is_null = 0 ;
}
if ( mysql_stmt_bind_param ( stmt, params) )
{
print_stmt_error ( stmt, "Cannot bind parameters" ) ;
return 1 ;
}
int_data[ 0 ] = 4 ;
int_data[ 1 ] = 0 ;
int_data[ 2 ] = 0 ;
if ( mysql_stmt_execute ( stmt) )
{
print_stmt_error ( stmt, "Cannot execute statement" ) ;
return 1 ;
}
return 0 ;
}
在执行CALL后,下面处理其结果
1.在此存储过程中执行的语句,每一个都会产生一个结果集
如SHOW/SELECT/....
2.如此存储过程有OUT或INOUT参数,
则还会有一个附加的单行结果集--其中含最终的参数值,且排列顺序与它们出现在过程定义中的顺序一致.
3.最终状态包
没有结果集与它关联,你可把它与语句或参数结果集区别开.
1.通过获得下一个结果的列数,可确定它是否含有结果集
或是否为最终状态包.
如列数为0,则它是状态包.无需进一步处理.
2.如列数大于0,
则表示有一个有很多个列的结果集
此结果集,可由某个语句产生,也可能含存储过程返回的那些参数值.
3.通过调mysql_stmt_next_result()
可检查是否还有更多的结果
如还有更多结果,函数返回0.
如无,返回-1.
如有错误,返回一个大于0的值.
static void process_call_result ( MYSQL* conn, MYSQL_STMT* stmt)
{
int status;
int num_cols;
do
{
if ( ( num_cols = mysql_stmt_field_count ( stmt) ) > 0 )
{
if ( conn-> server_status & SERVER_PS_OUT_PARAMS)
{
printf ( "OUT/INOUT parameter values:\n" ) ;
}
else
{
printf ( "Statement result set values:\n" ) ;
}
if ( process_result_set ( stmt, num_cols) )
{
break ;
}
}
status = mysql_stmt_next_result ( stmt) ;
if ( status > 0 )
{
print_stmt_error ( stmt, "Error checking for next result" ) ;
}
} while ( status == 0 ) ;
}