6:MySQL-SQL程序设计

- 编译&链接
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)
{
    
    
	// err
}
else
{
    
    
	// suc
}
所有语句可粗略划分为两大类
一类用于修改行,
一类会返回一个结果集
结果集,会通过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
	{
    
    
		// 若存在返回结果集,结果集中每一行的列数
		// 返回结果集下,列数至少为1
		// 不返回结果集下,获取的列数将为0
		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;
// res_set基于mysql_store_result得到
mysql_field_seek(res_set, 0);
for(i=0; i < mysql_num_fields(res_set); i++)
{
    
    
	field = mysql_fetch_field(res_set);
	// field->name为列标题
	// field->max_length列中最长数据值长度
	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);
	}
	
	// 输出\n
	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
};

// 把文件里的图像加载到picture表
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);
}

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/111410473
今日推荐