赞
踩
SQL的执行可大致分为下面两种模式:
“Immediate Statements” VS “Prepared Staements” :
动态的根据传入的参数拼接SQL语句并执行,一条语句经过MySQL server层分析器、优化器、执行器组件,分别进行词法、语义解析、优化SQL语句、选择索引、制定执行计划、执行并返回结果。
对SQL语句进行词法语义分析、优化SQL语句、选择索引、制定执行计划等一系列操作,称为 “对SQL语句的编译”。
如上,一条SQL语句按照此流程处理,一次编译,单次运行,此类普通语句被称作 “Immediate Statements”(即时SQL)。
例如:
bool CUserModel::getUser(uint32_t nUserId, DBUserInfo_t &cUser) { CDBConn* pDBConn = CDBManager::getInstance()->GetDBConn("teamtalk_slave"); if(pDBConn) { //根据函数外部传入的参数 nUserId,动态构造 select查询语句并执行: string strSql = "select * from IMUser where id = " + int2string(nUserId); CResultSet* pResultSet = pDBConn->ExcuteQuery(strSql.c_str()); if(pResultSet) { while(pResultSet->Next()) { //... } } } }
但是,绝大多数情况下,一般会需要一条SQL语句反复调用执行(例如上面的查找IMUser表中的用户信息,每次客户端向服务器请求登录验证时都需要执行一次),或者每次执行的时候只有个别的值不同(比如select的where子句值不同,update的set子句值不同,insert的values子句值不同)。
如果每次都需要经过上面的SQL编译过程(词法语义分析、语句优化、制定执行计划等),则效率明细会受到影响。
所谓 “预编译SQL语句”,就是将此类SQL语句中的某些值使用 “占位符” 替代,可以视为将SQL语句 “模板化” 或者说 “参数化”。一般称这类语句为 “Prepared Statements”。
预编译SQL语句的优势在于:一次编译、多次运行,省去了解析、优化等过程。此外使用预编译SQL语句还能防止SQL注入,下文展开。
(1)先与MySQL数据库取得连接,获得 “连接句柄” MYSQL*
:
MYSQl* mysql_init();
mysql_options();
mysql_real_connect(MYSQL*, ip, user_name, passed, db_name, port);
(2)基于这个 MYSQL*
连接句柄,初始化一个“预编译句柄”MYSQL_STMT*
:
MYSQL_STMT* mysql_stmt_init(MYSQL*);
(3)传入准备好的带有“占位符”的SQL语句,进行编译:
mysql_stmt_prepare(MYSQL_STMT*, sql.c_str(), sizeof(sql));
(4)在后面要使用这个预编译的SQL语句时,需要向其中传入实参填补“占位符”,所以我们必须要先将占位符的个数统计出来,并预先初始化一个 MYSQL_BIND
类型的结构体数组
(MYSQL_BIND[]数组的元素个数是SQL语句中占位符的个数,数组中每个元素是MYSQL_BIND结构体,用于指定某个占位符上的数据类型(如int) 及 数据值),等待使用时向其中填充参数:
uint32_t m_param_cnt = mysql_stmt_param_count(MYSQL_STMT*);
MYSQL_BIND* m_param_bind = new MYSQL_BIND[m_param_cnt]; //新建一个数组
(5)在使用时,先给 MYSQL_BIND[]
数组填充值:
for(int index = 0; index < m_param_cnt; index++)
{
//如果value是int型:
MYSQL_BIND[index].buffer_type = MYSQL_TYPE_LONG;
MYSQL_BIND[index].buffer = &value;
/*
//如果value是string型:
MYSQL_BIND[index].buffer_type = MYSQL_TYPE_LONG;
MYSQL_BIND[index].buffer = (char*)value.c_str();
MYSQL_BIND[index].buffer_length = value.size();
*/
}
(6)向填充好实参的MYSQL_BIND数组传入MYSQL_STMT句柄,随后执行这条SQL语句,并检查执行结果:
msyql_stmt_bind_param(m_stmt, m_bind_param);
mysql_stmt_excute(m_stmt); //如果有错误发生,函数返回非0,使用 mysql_stmt_error(m_stmt);可检查错误原因
mysql_stmt_affected_rows(m_stmt) == 0;
实现一个 CPrepareStatement
类,封装 MYSQL_STMT* 和 MYSQL_BIND* 对象,即相应的SQL预编译方法:
//cpreparestatement.h class CPrepareStatement { public: CPrepareStatement() {} ~CPrepareStatement() {} bool Init(MYSQL* mysql, string& sql); void SetParam(uint32_t index, int& value); void SetParam(uint32_t index, uint32_t& value); void SetParam(uint32_t index, string& value); void SetParam(uint32_t index, const string& value); bool ExecuteUpdate(); uint32_t GetInsertId(); private: MYSQL_STMT* m_stmt; MYSQL_BNID* m_param_bind; uint32_t m_param_cnt; }; //cpreparement.cpp bool CPrepareStatement::Init(MYSQL* mysql, string& sql) { mysql_ping(mysql); m_stmt = mysql_stmt_init(mysql); if(!m_stmt) { return false; } if(mysql_stmt_prepare(m_stmt, sql.c_str(), sql.size())) { printf("%s\n", mysql_stmt_error(m_stmt)); return false; } m_param_cnt = mysql_stmt_papram_count(m_stmt); if(m_param_cnt > 0) { m_param_bind = new MYSQL_BIND[m_param_cnt]; if(!m_param_bind) { return false; } } memset(m_param_bind, 0, sizeof(MYSQL_BIND) * m_param_cnt); return true; } //注意:给int型和string型赋值的方式是不同的: void CPrepareStatement::SetParam(uint32_t index, int& value) { if(index >= m_param_cnt) return; m_param_bind[index].buffer_type = MYSQL_TYPE_LONG; m_param_bind[index].buffer = &value; } void CPrepareStatement::SetParam(uint32_t index, uint32_t& value) { if(index >= m_param_cnt) return; m_param_bind[index].buffer_type = MYSQL_TYPE_LONG; m_param_bind[index].buffer = &value; } void CPrepareStatement::SetParam(uint32_t index, string& value) { if(index >= m_param_cnt) return; m_param_bind[index].buffer_type = MYSQL_TYPE_LONG; m_param_bind[index].buffer = (char*)value.c_str(); m_param_bind[index].buffer_length = value.size(); } void CPrepareStatement::SetParam(uint32_t index, const string& value) { if(index >= m_param_cnt) return; m_param_bind[index].buffer_type = MYSQL_TYPE_LONG; m_param_bind[index].buffer = (char*)value.c_str(); m_param_bind[index].buffer_length = value.size(); } bool CPrepareStatement::ExecuteUpdate() { if(!m_stmt) return false; if(mysql_stmt_bind_param(m_stmt, m_param_bind)) { printf("%s\n", mysql_stmt_error(m_stmt)); return false; } if(mysql_stmt_execute(m_stmt)) { printf("%s\n", mysql_stmt_error(m_stmt)); return false; } if(msyql_affected_rows(m_stmt) == 0) { printf("no affect\n"); return false; } return true; } uint32_t CPrepareStatement::GetInsertId() { return mysql_stmt_insert_id(m_stmt); }
使用 class CPrepareStatement
类执行insert into插入操作:
bool CMessageModel::sendMessage(uint32_t nRelateId, uint32_t nFromId, uint32_t nToId, IM::BaseDefine::MsgType nMsgType, uint32_t nCreateTime, uint32_t nMsgId, string& strMsgContent) { CDBConn* pDBConn = CDBManager::getInstance()->GetDBConn("teamtalk_slave"); if(pDBConn) { string strTableName = "IMMessage_" + int2string(nRelateId % 8); string strSql = "insert into " + strTableName + " ('relateId', 'fromId', 'toId', 'msgId', 'content', 'status', 'type', 'created', 'updated') values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; shared_ptr<CPrepareStatement> pStmt = make_shared<CPrepareStatement>(); if(pStmt->Init(pDBConn->GetMysql(), strSql)) { uint32_t nStatus = 0; //表示查询未被删除的记录 pStmt->SetParam(index++, nRelateId); pStmt->SetParam(index++, nFromId); pStmt->SetParam(index++, nToId); pStmt->SetParam(index++, nMsgId); pStmt->SetParam(index++, strMsgContent); pStmt->SetParam(index++, nStatus); pStmt->SetParam(index++, nMsgType); pStmt->SetParam(index++, nCreateTime); pStmt->SetParam(index++, nCreateTime); pStmt->ExecuteUpdate(); } //delete pStmt; 使用shared_ptr智能指针,不必delete删除 pDBManager->RelDBConn(pDBConn); //这里同样可以使用RAII的方法实现自动释放,在 CDBConn类对象析构的时候释放连接 } }
MYSQL_BIND()
函数中的参数类型如下表所示,可见 MYSQL_TYPE_LONG
表示的是 4字节的int
型。
# SQL注入与MySQL预编译:
IM项目中只有在 “insert into” 向表中插入数据时,才会使用 CPrepareStatement 预处理,
因为只有这个时候才会发生 “SQL注入”。
而MySQL预处理不仅 可以防止SQL注入,还有提高执行效率的作用:
《“即时SQL” 与 “预处理SQL” 的区别》:
https://www.cnblogs.com/geaozhang/p/9891338.html
参考链接:
https://dev.mysql.com/doc/c-api/5.6/en/c-api-prepared-statement-type-codes.html
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。