Google
 
Back to index
MySQL 的基本使用
MySQL 参数设置
MySQL 的 C++ 接口编程
Windows 下安装 MySQL Connector/C++
程序的基本编写思路
编程注意事项
疑难杂症
MySQL Connector/C++ 的 setBlob()

MySQL 的基本使用

从命令行连接 MySQL 数据库,可以使用下面的方法:

shell> mysql —user=user_name —password=your_password db_name

如果你在你本机装了一个 MySQL 程序,可以使用 mysql 和 mysqld 这两个命令,那这两个命令有什么区别呢?

mysql 是一个连接 MySQL 的客户端 ( client program),而 mysqld 则是服务程序。

MySQL 参数设置

对于 MySQL 的客户端来说,也是有一些变量可以设置的,如果你用上面的方法登陆到了 mysql 客户端程序里,可以用下面的方法来设置:

mysql> SET max_sort_length = 2000;

对于 MySQL 来说,有很多服务器参数可以设置。

我们可以用下面的命令来查询一下这些参数:

mysqld --verbose --help

如果我们需要设置 max_allowed_packet 这个参数,我们可以用类似下面的命令来启动 MySQL:

mysqld --max_allowed_packet=1000M --verbose --help

当然,最好的办法是你修改 MySQL 的配置文件,在 Windows 下这个文件默认是安装目录下的 my.ini 文件,而在 Linux 下则是 /etc/my.cnf 文件。

MySQL 的 C++ 接口编程

我们在不同的应用场合使用 MySQL 数据库,当然需要根据不同的程序提供不同的 MySQL 接口。MySQL 官方就提供了 Python, jave, php 等很多编程语言的接口,对于 C++ 来说,我们可以使用 MySQL Connector/C++ 这个库。

在 Windows 下可以使用 Visual Studio 来编写 MySQL 客户端,开始编程的时候,当然是学习一下官方的实例1实例2了。

Windows 下安装 MySQL Connector/C++

Windows 下安装 MySQL 最简单的方法当然是用二进制包进行安装,而官方文档也对在 Windows 下从源代码编译安装 MySQL Connector/C++ 进行了详细的论述。

程序的基本编写思路

MySQL 的 Connector/C++ 有两种实现存储过程的方法,一种是直接用 SQL 语句 ( Statements ) 的方法,一种是间接使用 SQL 语句 ( Prepared Statements ) 的方法。

这两种方法的主要区别是,第一种方法一次运行一条 SQL 语句,较容易理解与掌握,对于简单的 SQL 操作,比较方便;而第二种方法则可以对一条预设的 SQL 语句多次插入数值、填充 SQL 语句里预留下来的数据项,对于较长、较复杂的 SQL 语句比较方便。

MySQL Connector/C++ 手册里的实例 1 基本上就是用的第一种方法,节选代码参考如下:

#include <stdlib.h>
#include <iostream>

#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

using namespace std;

int main(void)
{
    cout << endl;
    cout << "Running 'SELECT 'Hello World!' AS _message'..." << endl;

    try {
        sql::Driver *driver;
        sql::Connection *con;
        sql::Statement *stmt;
        sql::ResultSet *res;

        /* Create a connection */
        driver = get_driver_instance();
        con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
        /* Connect to the MySQL test database */
        con->setSchema("test");

        stmt = con->createStatement();
        res = stmt->executeQuery("SELECT 'Hello World!' AS _message");
        while (res->next()) {
            cout << "\t... MySQL replies: ";
            /* Access column data by alias or column name */
            cout << res->getString("_message") << endl;
            cout << "\t... MySQL says it again: ";
            /* Access column fata by numeric offset, 1 is the first column */
            cout << res->getString(1) << endl;
        }
        delete res;
        delete stmt;
        delete con;

    } catch (sql::SQLException &e) {
        cout << "# ERR: SQLException in " << __FILE__;
        cout << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;
        cout << "# ERR: " << e.what();
        cout << " (MySQL error code: " << e.getErrorCode();
        cout << ", SQLState: " << e.getSQLState() << " )" << endl;
    }

    cout << endl;

    return EXIT_SUCCESS;
}

MySQL Connector/C++ 手册里的实例 2 用的是第二种方法,先准备一条 SQL 语句,里面需要插入真实数据的地方用 ? 来标志出位置(这个 ? 号叫 place holder),然后我们再根据一系列的语句来设置这些 ? 号的真实值。这种方法,总体来说,灵活性强一些,适合字段比较多的情况。节选参考代码如下:

#include <stdlib.h>
#include <iostream>

#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>

using namespace std;

int main(void)
{
    cout << endl;
    cout << "Let's have MySQL count from 10 to 1..." << endl;

    try {
        sql::Driver *driver;
        sql::Connection *con;
        sql::Statement *stmt;
        sql::ResultSet *res;
        sql::PreparedStatement *pstmt;

        /* Create a connection */
        driver = get_driver_instance();
        con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
        /* Connect to the MySQL test database */
        con->setSchema("test");

        stmt = con->createStatement();
        stmt->execute("DROP TABLE IF EXISTS test");
        stmt->execute("CREATE TABLE test(id INT)");
        delete stmt;

        /* '?' is the supported placeholder syntax */
        pstmt = con->prepareStatement("INSERT INTO test(id) VALUES (?)");
        for (int i = 1; i <= 10; i++) {
            pstmt->setInt(1, i);
            pstmt->executeUpdate();
        }
        delete pstmt;

        /* Select in ascending order */
        pstmt = con->prepareStatement("SELECT id FROM test ORDER BY id ASC");
        res = pstmt->executeQuery();

        /* Fetch in reverse = descending order! */
        res->afterLast();
        while (res->previous())
            cout << "\t... MySQL counts: " << res->getInt("id") << endl;
        delete res;

        delete pstmt;
        delete con;

    } catch (sql::SQLException &e) {
        cout << "# ERR: SQLException in " << __FILE__;
        cout << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;
        cout << "# ERR: " << e.what();
        cout << " (MySQL error code: " << e.getErrorCode();
        cout << ", SQLState: " << e.getSQLState() << " )" << endl;
    }

    cout << endl;

    return EXIT_SUCCESS;
}

编程注意事项

MySQL Connector/C++ 的编程比较简单,但是有几点需要注意:

sql::Connection *con;
sql::Statement *stmt;
sql::ResultSet *res;

/* Create a connection */
driver = get_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
/* Connect to the MySQL test database */
con->setSchema("test");

疑难杂症

MySQL Connector/C++ 的 setBlob()

我在用 setString 来插入一个 100K 的 BLOB 数据时,Connector/C++ 提示了如下的错误:

# ERR: SQLException in e:\vaultusa\supervault\src\branches\sprint_9_dev\fe_svs_with_mysql\test\db-test-mysql.cpp(batchExecute) on line 86
# ERR: Data too long for column 'KEY_ARRAY' at row 1 (MySQL error code: 1406, SQLState: 22001 )

我们来看看 Connector/C++ notes 里的原话(答案就在里面):

When inserting or updating BLOB or TEXT columns, MySQL Connector/C++ developers are advised not to use setString(). Instead it is recommended that the dedicated API function setBlob() be used instead.

The use of setString() can cause a Packet too large error message. The error will occur if the length of the string passed to the connector using setString() exceeds max_allowed_packet (minus a few bytes reserved in the protocol for control purposes). This situation is not handled in MySQL Connector/C++, as this could lead to security issues, such as extremely large memory allocation requests due to malevolently long strings.

However, if setBlob() is used, this problem does not arise. This is because setBlob() takes a streaming approach based on std::istream. When sending the data from the stream to MySQL Server, MySQL Connector/C++ will split the stream into chunks appropriate for MySQL Server and observe the max_allowed_packet setting currently being used.

如果你要把一个很长的 std::string 插入到 MySQL 的 Blob 字段里,则可以把 std::string 转成 std::istream 之类的数据,参考下面的代码:

#include <iostream>
#include <sstream>

void f( std::istream& in )
{
  std::string s;
  in >> s;
  std::cout << s << std::endl;
}

int main()
{
  std::string s("hello");
  std::istringstream ss( s );

  f( ss );
}

当然,出现这么一个问题,你还得考虑一下,是不是 Blob 字段本身的尺寸不满足你插入数据的要求。Blob 字段的数据虽然很大,但是还是有大小限制的