C++拼接SQL语句的高效方法

访问数据库的应用程序一般都通过字符串拼接的方式来构造SQL语句。例如,可以使用boost的format,或者类似的拼接方法:

1
2
3
4
5
6
std::string sql = boost::str(boost::format("select %1%, %2%, %3%, %4% from %5% where %1% = ?;")
% kUserIdColumnName
% kUserNameColumnName
% kUserEmailColumnName
% kUserPhoneColumnName
% kUserTableName);

上面的例子构造了一条从用户信息表获取用户信息的SQL语句,这并没有什么问题。但仔细想想,在运行时构造SQL语句是毫无必要的,因为这种SQL语句基本上不会在程序运行的时候改变。假如每次查询都要进行这样的拼接过程,那么这部分的性能就白白浪费掉了。另一种做法是把sql变量定义成局部静态变量,这样只需要拼接一次就可以了。不过要是这段代码运行在并发环境中,还需要考虑线程安全的问题,这会带来一些额外的工作。

当然啦,这种重复构造字符串本身的开销是非常小的,一般不会造成性能问题。但如果你是完美主义者,希望榨干最后一滴性能,那么可以考虑一下下面介绍的方法。

这种方法基于C++的字符串语法糖:

1
2
3
const char* string = "Curlion is " "a C++ wrapper"
" for libcurl’s " "multi socket "
"interface.";

上述语句等效于:

1
const char* string = "Curlion is a C++ wrapper for libcurl’s multi socket interface.";

也就是说,如果两个字符串字面量之间只包含空白字符,那么它们会被合并成一个。这个合并发生在编译时,不消耗运行时时间。

为了使用这种拼接方式,我们首先要把表名和列名变量修改成用宏来定义:

1
2
3
4
5
#define kUserTableName "user"
#define kUserIdColumnName "id"
#define kUserNameColumnName "name"
#define kUserEmailColumnName "email"
#define kUserPhoneColumnName "phone"

然后就可以修改拼接方式了:

1
2
3
4
5
6
7
8
const char* sql = "select "
kUserIdColumnName ", "
kUserNameColumnName ", "
kUserEmailColumnName ", "
kUserEmailColumnName ", "
kUserPhoneColumnName
" from " kUserTableName
" where " kUserIdColumnName " = ?;";

这样一来,sql相当于一个常量,不需要初始化,完全不消耗运行时的性能。另外,这么做也有一个好处,就是使得SQL语句的可读性有了一定提高。使用format的方法经常要手工一个个地去匹配参数列表,很繁琐也容易出错。特别是那些有一串长长的参数列表的复杂语句,看起来很恐怖。现在这种方法则好得多,参数直接放在了它所在的位置,一目了然。

使用宏来定义表名和列名可能会令人感到不安。但我觉得这是宏的合理使用,不会带来什么不良后果,因为表名和列名基本上只会用于SQL语句拼接的场景。有时候为了追求性能或者其他方面的目标,的确要牺牲一些代码的“观赏性”。