接着上篇文章继续讲字符集的故事。这一篇文章主要讲MYSQL的各个字符集设置,关于基础理论部分,参考于这里

 

1. MYSQL的系统变量

character_set_server:默认的内部操作字符集

character_set_client:客户端来源数据使用的字符集

character_set_connection:连接层字符集

character_set_results:查询结果字符集

character_set_database:当前选中数据库的默认字符集

character_set_system:系统元数据(字段名等)字符集

简单来说,对于使用MYSQL C API的我们来说,主要关心的是3个字符集,即character_set_client, character_set_connection和character_set_results。但是从我的使用的角度上来说,总觉得character_set_connection有点多余。

 

2. MySQL中的字符集转换过程

这一节完全盗版的http://www.laruence.com/2008/01/05/12.html。为了阅读起来方便,再贴一遍。

1) MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;

2) 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:

• 使用每个数据字段的CHARACTER SET设定值;

• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);

• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;

• 若上述值不存在,则使用character_set_server设定值。

3) 将操作结果从内部操作字符集转换为character_set_results。

上面从character_set_connection转换到内部操作字符集的过程看起来比较复杂,但是如果我们在MYSQL建表的时候指定了数据表的字符集,就可以简单认为这个“内部操作字符集”就是对应表的字符集。所以说,我比较推荐在建表的时候带上这句话“DEFAULT CHARSET=xxx”,其中的xxx可以通过”select character_set_name from information_schema.CHARACTER_SETS”来获取。建议是”UTF8”。

 

3. MySQL中的字符集转换实验

我这里的环境是这样的。

  • main.cpp是utf-8格式的,编译的gcc并没有指定finput-charset和fexec-charset,所以可执行文件中的中文应该也是以utf-8的方式存储的;
  • linux终端环境是de_DE(export LANG=de_DE)
  • MYSQL中的建表语句是

      CREATE TABLE `tbl_test` (
      `id` int ,
      name varchar(20000),
      uptime date,
      PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8

 

实验一:正确地处理中文的过程

这个实验的大致过程是,

  • 在连接之后,使用set names utf8同时设置了character_set_client, character_set_connection和character_set_results。
  • 通过set character_set_client=gbk设置客户端的字符集。
  • 在代码中硬编码出“insert into tbl_test(id, name, uptime) VALUES (100, ‘你好’, ‘20130101’)
  • 然后调整那个”好“为”饕“。

 

需要注意的点是,我首先将二进制中的硬编码(utf8格式)的char*串转换成wchar_t*串,然后调整中文。在出去之前再将wchar_t*串调整为gbk的char*串。经过试验,下面的代码运行正常。

#include <vector>
#include <string>
#include <tr1/memory>
#include <sstream> #include "common/dbcomm/DbComm.h" using namespace std; COMMON::DbLocation dbLocation1; void InsertBySqlStatmentTest1(); int main()
{
dbLocation1.SetDbId("TEST_DB1");
dbLocation1.SetIp("127.0.0.1");
dbLocation1.SetPort("3306");
dbLocation1.SetUser("cup_dba");
dbLocation1.SetPassword("123456"); InsertBySqlStatmentTest1(); return 0;
} void InsertBySqlStatmentTest1()
{
try
{
vector<COMMON::DbLocation> dbLocations_array;
dbLocations_array.push_back(dbLocation1);
dbLocations_array.push_back(dbLocation2); tr1::shared_ptr<COMMON::IDbTasks> mysqlTasks( new COMMON::MysqlDbTasks(dbLocations_array, true) );
mysqlTasks->Connect(); cout << "Connect success" << endl; {
COMMON::DbExecuteAction* char_action = mysqlTasks->Execute();
COMMON::ExecuteFilter char_filter("set names utf8");
char_action->Do(&char_filter, &dbLocation1); // change the character_set_client to gbk
COMMON::ExecuteFilter char_filter2("SET character_set_client = gbk");
char_action->Do(&char_filter2, &dbLocation1);
char_action->EndAction();
} COMMON::DbExecuteAction* insert_action = mysqlTasks->Insert(5000); stringstream ss;
ss << "INSERT INTO tbl_test(id, name, uptime) VALUES" << "(" << 100 << "," << "'你好'," << "'20130101')"; string statement = ss.str(); // use mbstowcs to change the sql statement to wide-char-string
// we use the default value of fexec-charset, which is utf-8, to compile this file with gcc.
setlocale(LC_ALL, "zh_CN.utf8");
size_t wcs_size = mbstowcs(NULL, statement.c_str(), 0);
wchar_t* dest = new wchar_t[wcs_size + 1];
wmemset(dest, L'\0', wcs_size + 1);
mbstowcs(dest, statement.c_str(), statement.size() * sizeof(char)); // change the last '好' to '饕'
wchar_t *tmp = wcsrchr(dest, L'好');
*tmp = L'饕'; // change the sql statement to the charset that corresponds to the character_set_client of mysql
setlocale(LC_ALL, "zh_CN.gbk");
size_t mbs_size = wcstombs(NULL, dest, 0);
char* buf_mbs = new char [mbs_size + 1];
memset(buf_mbs, '\0', mbs_size + 1);
wcstombs(buf_mbs, dest, wcs_size * sizeof(wchar_t)); // try to insert into mysql
COMMON::InsertFilter insertFilter(buf_mbs);
insert_action->Do(&insertFilter);
insert_action->EndAction(); cout << "EndAction success" << endl; mysqlTasks->Disconnect(); cout << "Disconnect success" << endl;
}
catch (COMMON::ThrowableException& e)
{
cout << e.What() << endl;
}
catch (...)
{
cout << "unknown exception" << std::endl;
}
}

实验二:错误地处理中文的过程

现在来做一些修改,我们先把情况变得简单一些,我们不恶意地去set character_set_client=gbk,而是只运行set names utf8。然后在拿到拼凑好的sql语句的时候,利用string::find方法找到‘你’,然后直接利用结果的数字下标来修改成‘饕’。具体的代码如下

#include <vector>
#include <string>
#include <tr1/memory>
#include <sstream> #include "common/dbcomm/DbComm.h" using namespace std; COMMON::DbLocation dbLocation1; void InsertBySqlStatmentTest1(); int main()
{
dbLocation1.SetDbId("TEST_DB1");
dbLocation1.SetIp("127.0.0.1");
dbLocation1.SetPort("3306");
dbLocation1.SetUser("cup_dba");
dbLocation1.SetPassword("123456"); InsertBySqlStatmentTest1(); return 0;
} void InsertBySqlStatmentTest1()
{
try
{
vector<COMMON::DbLocation> dbLocations_array;
dbLocations_array.push_back(dbLocation1); tr1::shared_ptr<COMMON::IDbTasks> mysqlTasks( new COMMON::MysqlDbTasks(dbLocations_array, true) );
mysqlTasks->Connect(); cout << "Connect success" << endl; {
// ************这里不再恶作剧地修改character_set_client为gbk**************
COMMON::DbExecuteAction* char_action = mysqlTasks->Execute();
COMMON::ExecuteFilter char_filter("set names utf8");
char_action->Do(&char_filter, &dbLocation1);
char_action->EndAction();
} COMMON::DbExecuteAction* insert_action = mysqlTasks->Insert(5000); stringstream ss;
ss << "INSERT INTO tbl_test(id, name, uptime) VALUES" << "(" << 100 << "," << "'你好'," << "'20130101')"; // ************直接修改string**************
string statement = ss.str();
size_t pos = statement.find('你');
statement[pos] = '饕'; // try to insert into mysql
COMMON::InsertFilter insertFilter(statement);
insert_action->Do(&insertFilter);
insert_action->EndAction(); cout << "EndAction success" << endl; mysqlTasks->Disconnect(); cout << "Disconnect success" << endl;
}
catch (COMMON::ThrowableException& e)
{
cout << e.What() << endl;
}
catch (...)
{
cout << "unknown exception" << std::endl;
}
}

 

结果是,

为了追寻错误的原因,让我们从十六进制的角度来看。

可以看到,

        size_t pos = statement.find('你');
statement[pos] = '饕';

 

实质只改动了一个字节(utf8编码,从‘你’的E4BDA0到‘何’的E4BC95,我们的改动,就是那个95,他是‘饕’的一个字节。)这个现象也符合我们对于string行为的认识。

 

4. 总结和建议

  • 建议对于每一张数据表都设置字符集
  • 建议把character_set_client, character_set_connection和character_set_results设置为和数据表的字符集一致
  • 在使用MYSQL C API的时候,初始化数据库句柄后马上用mysql_options设定MYSQL_SET_CHARSET_NAME属性为与数据表的字符集一致的字符集,或者通过发送SQL语句set names xxx来设置字符集。
  • 如果需要处理中文,那么数据表的字符集通常是utf-8或者gbk。
  • 如果要对中文做字符处理,那么就一定要根据实际的情况设置setlocale,使用mbstowcs转换成wcs,然后针对wide-char string进行操作,再使用wcstombs转换为多字节字符串拼成sql语句传递给数据库连接。

字符集与Mysql字符集处理(二)的更多相关文章

  1. 字符集与Mysql字符集处理(一)

      一.字符集总结 其实大多数的知识在这篇文章里已经讲得非常清楚了.这里只是讲一下自己的感悟. 1. UTF-8虽然是以UTF(unicode transfermation format)开头的,但是 ...

  2. 9.Mysql字符集

    9.字符集9.1 字符集概述 字符集就是一套文字符号及其编码.比较规则的集合. ASCII(American Standard Code for Information Interchange)字符集 ...

  3. MySQL从删库到跑路(二)——MySQL字符集与乱码解析

    作者:天山老妖S 链接:http://blog.51cto.com/9291927 一.字符集与编码 1.字符集简介 字符(Character)是各种文字和符号的总称,包括各国家文字.标点符号.图形符 ...

  4. 如何修改MySQL字符集

    首先,MySQL的字符集问题主要是两个概念,一个是Character Sets,一个是Collations,前者是字符内容及编码,后者是对前者进行比较操作的一些规则.这两个参数集可以在数据库实例.单个 ...

  5. MySQL 字符集设置

    /*************************************************************************** * MySQL 字符集设置 * 说明: * 数 ...

  6. [MySQL] 字符集的选择

    1. Mysql支持的字符集 MySQL服务器可以支持多种字符集,不同的字段都可以使用不同的字符集. 查看所有可用字符集: show character set; select * from info ...

  7. (转载)查看三种MySQL字符集的方法

    (转载)http://database.51cto.com/art/201010/229171.htm MySQL字符集多种多样,下面为您列举了其中三种最常见的MySQL字符集查看方法,该方法供您参考 ...

  8. 查看mysql字符集及修改表结构--表字符集,字段字符集

    MySQL 乱码的根源是的 MySQL 字符集设置不当的问题,本文汇总了有关查看 MySQL 字符集的命令.包括查看 MySQL 数据库服务器字符集.查看 MySQL 数据库字符集,以及数据表和字段的 ...

  9. 查看mysql字符集及修改表结构

    MySQL 乱码的根源是的 MySQL 字符集设置不当的问题,本文汇总了有关查看 MySQL 字符集的命令.包括查看 MySQL 数据库服务器字符集.查看 MySQL 数据库字符集,以及数据表和字段的 ...

随机推荐

  1. paip.windows io监控总结

    paip.windows io监控总结 io的主要参数是个.disk queue length 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专 ...

  2. iOS开发---集成ShareSDK实现第三方登录、分享、关注等功能。

    (1)官方下载ShareSDK IOS 2.9.6,地址:http://sharesdk.mob.com/Download (2)根据实际情况,引入相关的库,参考官方文档. (3)在项目的AppDel ...

  3. 安装 Autoconf 2.69版

    发生错误configure.ac:8: error: Autoconf version 2.64 or higher is required 1.检查版本 [root@localhost Deskto ...

  4. CreateJSのTweenJS、SoundJS、PreloadJS

    TweenJS基础实例: var canvas = document.getElementById('myCanvas'), stage = new createjs.Stage(canvas); v ...

  5. UWP开发-二维变换以及三维变换

    在开发中,由于某些需求,我们可能需要做一些平移,缩放,旋转甚至三维变换,所以我来讲讲在UWP中这些变换的实现方法. 一. 二维变换: UIElement.RenderTransform a.Trans ...

  6. Call and Apply in JavaScript

    Call 和 Apply 方法可以用来代替另一个对象调用一个方法,改变this指向. 1.call -call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) -调用一 ...

  7. 什么是automatic variable?

    看代码符号$?搞不清楚是什么?   看代码. $share = Get-WmiObject -Class Win32_Share -ComputerName $Server.name -Credent ...

  8. Google Interview University - 坚持完成这套学习手册,你就可以去 Google 面试了

    作者:Glowin链接:https://zhuanlan.zhihu.com/p/22881223来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 原文地址:Google ...

  9. CentOS6.5下安装Open vSwitch

    准备 # yum install openssl-devel redhat-rpm-config kernel-devel -y #yum install kvm libvirt python-vir ...

  10. 高级屏幕空间反射: Screen Space Reflection (SSSR)

    SSSR进一步调优,对标寒霜级技术水平,实现方式为Direct3D 11+自主实现实时渲染引擎,方法为对比测试.实现已经有段时间了,还是简要更新下吧.以下画面中的SSSR效果全部采用1:4 resol ...