关于用Python操作oracle出现乱码处理的一点小小心得
前言
这几天使用Python读取和写入数据到oracle,感觉快要崩溃,在公司同事的指点下,今天实验,终于解决,在此记录下心得和思考解决问题时的思维路线和过程。
本机实验环境
系统环境:ubuntu 14.04
python版本:python2.7
oracle版本:oracle 11g
调试工具:ipdb
查看数据:Oracle SQL Developer
过程分析
前提与假设:
- 查询语句:
select * from test
- 插入语句:
insert into test(col1, col2, col3) values('Hello world', '世界', '你好')
- 用sqlalchemy操作数据,不使用数据模型,大致代码如下,cour为操作游标
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('oracle://user:pwd@localhost:1521/xe')
cour = sessionmaker(bind=engine)()
ssql = "select * from test"
isql = "insert into test(col1, col2, col3) values('Hello world', '世界', '你好')"
#查询语句,返回一条数据
ans = cour.execute(ssql).fetchone()
#执行插入语句并提交
cour.execute(isql)
cour.commit()
问题的出现
像往常操作其它数据库一样,当执行cour.execute(isql)
时报UnicodeEncodeError
错误,这是python2.7中最常见的编码错误,一般将错误语句执行unicode_str.encode('utf8')
就可以解决,如还不行则前面加入import os;reload(sys);sys.setdefaultencoding('utf8')
,点击查看sys模块的使用 🔗,这些加入后还是报UnicodeEncodeError错误
尝试解决:
- 查看堆栈信息,发现最后一句报错语句是
/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/default.py
的470句cursor.execute(statement, parameters)
,其中statement为要执行的sql语句 - 此句前设置断点,尝试单步调试进入此句失败,猜测可能是cx_Oracle里的哪个二进制文件
- 尝试将执行的sql语句转换编码成utf-8、utf-16、gbk、gb2312、gb180xxx、unicode,发现到
cursor.execute(statement, parameters)
时都会被转换成unicode,此句执行unicode语句报错,将这句的statement编码转换成utf-8编码,执行成功,当时那个兴奋啊,还以为找到了bug,还继续调试查看哪里修改了语句的编码 - 绝望的是当在developer软件中查看中文显示全是问号,sqlplus显示连问号都不见了,实验失败!
实验失败只能请问google和栈爆stackoverflow 🔗了,栈爆网查找问题,由于这个问题大部分出现在中国开发者这里,国外社区很少有这类问题,只能寄托在google了,没想到的是google搜索出来这篇内容的重复率非常高,如下:
oracle数据库版本是10g,字符集是AL32UTF8.
编写的python脚本中需要加入如下几句:
import os
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
这样可以保证select出来的中文显示没有问题。
要能够正常的insert和update中文,还需要指定python源文件的字符集密码和oracle一致。
# -*- coding: utf-8 -*-
......
......
抱着希望安装上面的实验,结果还是失败,现在想想可能是因为执行环境不同吧,因为别人都这么搞应该是有用的吧,如果将export NLS_LANG='SIMPLIFIED CHINESE_CHINA.UTF8'
写入~/.bashrc
然后运行程序就解决了,但是由于刚用oracle并不了解它,如果是postgresql的问题栈爆上肯定可以找到答案。翻看了google上的其它文章,学到了一些oracle的基础知识。
想想都可以知道我当时的绝望了,问同事都推荐大牛键哥,然后群里问,键哥给了一个文件里面四种方法,一一检查发现最后一种可能有用,就是配置环境变量export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
,果不其然,查询语句返回的中文不再是问号变成了gbk编码的中文。查看NLS_LANG的作用,只查到NLS_LANG是其它三个NLS环境变量的综合,包括语言、国家和编码方式。既然获得的数据中文变成了gbk编码了,那逆过程用gbk编码插入数据是不是可行呢?实验结果插入的还是问号,并且返回的也不是gbk编码的,也是问号,无奈只能问技术群,这次没有单独@键哥,万一其他人也碰到呢,问题如下:
有人遇到过吗,python读写oracle数据,当没有赋值NLS_LANG的时候,select返回的中文都是问号表示且不能做转换,当把NLS_LANG赋值成AMERICAN_AMERICA.ZHS16GBK后,select返回了gbk编码的中文。但是insert插入中文,developer查看还是显示问号,按理说既然select返回的gbk编码的中文,那insert插入gbk编码的不是应该没问题的吗,可还是显示问号,这到底怎么回事啊postgresql从来不会遇到编码问题啊,想不通
等了许久最后还是键哥回复了,给了一串AMERICAN_AMERICA.AL32UTF8
,告诉我可能developer显示的是utf-8编码的数据。在地铁上思考,可能确实是自己的解决问题的方式有问题。第二天也就是今天尝试,我去,竟然成功了,此处感谢键哥。
最终解决方式如下
向~/.bashrc
文件插入export NLS_LANG=AMERICAN_AMERICA.AL32UTF8
,保存后执行source ~/.bashrc
,然后重新打开一个命令窗口,执行运行命令,查看数据库,显示中文成功,执行select语句返回数据的编码直接是utf-8的中文。此处再次感谢键哥!同理export NLS_LANG=AMERICAN_AMERICA.UTF8
和export NLS_LANG='SIMPLIFIED CHINESE_CHINA.UTF8'
的编码方式为UTF8的都可以用。
总结
经过几天的困扰,问题终于解决,这种问题对于有经验积累的开发者简直就是小儿科,但对于初学者还是很难理解的,尤其是网上其他人可以解决的方法在我这里没用也是难以理解到崩溃。还有就是检查错误的根路线错误,按照之前的经验用ipdb调进去找关键代码查看问题的原因,回溯就可以解决问题,但这个sqlalchemy最后调用的cx_Oracle有点复杂,以至于最后执行的方法代码都跟丢了。对于这次的问题还是需要基础知识扎实的,如果知道环境变量的NLS_LANG的作用和重要性应该就不会有这种问题了。
那么最后的问题还是需要人解答,万分感谢:python读写oracle数据,当把NLS_LANG赋值成AMERICAN_AMERICA.ZHS16GBK后,select返回了gbk编码的中文。但是insert插入转为gbk编码中文的中文时显示的还是问号,那么为什么逆向处理行不通?