我有一个迁移,它正在运行一些依赖于unicode字符的自定义代码。我目前使用的是SQLAlchemy 1.1.9和Alembic 1.0.2。
我可以看到我的数据库和表都有正确的设置:
mysql> SELECT @@character_set_database, @@collation_database;
+--------------------------+----------------------+
| @@character_set_database | @@collation_database |
+--------------------------+----------------------+
| utf8mb4 | utf8mb4_general_ci |
+--------------------------+----------------------+
和
mysql> SHOW TABLE STATUS where name like 'mytable';
+---------+-----+--------------------+----------+----------------+---------+
| Name | ... | Collation | Checksum | Create_options | Comment |
+---------+-----+--------------------+----------+----------------+---------+
| mytable | ... | utf8mb4_unicode_ci | NULL | | |
+---------+-----+--------------------+----------+----------------+---------+
我插入了一个字符串,Nguyễn Johñ
(注意e和n都是unicode字符)。当我让我的flask应用程序加载行时,它会正确加载。但是当我运行迁移时,我看到alembic调试日志显示Nguy?n Johñ
和我自己的调试日志打印相同的东西。
为什么一些unicode字符被转换成问号?(注意测试其他字符,我在终端中看到一些字符,一些转义,如"xa0"
,和其他的"?"
。
以下内容可能也很重要。
- 发送给
engine = create_engine()
的URL具有utf8字符集 我有以下代码运行迁移:
from sqlalchemy.sql import table, column
from sqlalchemy import String, Integer, Boolean, Date, Unicode
MyTable = table('mytable',
column('id', Integer),
column('test1', Unicode(collation='utf8mb4_unicode_ci')),
column('test2', Unicode),
)
...
def upgrade():
...
bind = op.get_bind()
session = orm.Session(bind=bind)
rows = session.query(MyTable).all()
print(rows)
- 调试日志也显示以下内容,但我不确定这是否只是alembic自己的特征检测代码:
INFO [sqlalchemy.engine.base.Engine] show collation where `Charset` = 'utf8' and `Collation` = 'utf8_bin'
INFO [sqlalchemy.engine.base.Engine] ()
DEBUG [sqlalchemy.engine.base.Engine] Col ('Collation', 'Charset', 'Id', 'Default', 'Compiled', 'Sortlen')
DEBUG [sqlalchemy.engine.base.Engine] Row ('utf8_bin', 'utf8', 83, '', 'Yes', 1)
发现了这个问题,这是一个很小但非常重要的疏忽。
我有engine = create_engine()
在DB URL w/"charset=utf8"
,然而,config.set_main_option('sqlalchemy.url', db_url)
没有得到字符集。
一旦在两个地方都传递了字符集参数,一切都开始工作了。
我也可以确认Unicode(collation='utf8mb4_unicode_ci')
是不必要的,只用Unicode()
就足够了。
和DEBUG日志行仍然显示"utf8_bin"one_answers";utf8"所以我认为他们在做某种特征检测。
我只使用sqlite和mysql,所以我使用了这个片段:
if not db_url.startswith("sqlite"):
sep = "&" if "?" in db_url else "?"
db_url = "{db_url}{sep}charset=utf8".format(db_url=db_url, sep=sep)
我知道这对我的嫉妒很有效,但也可以适用于其他人。其他可能不需要阻止为sqlite添加字符集,并且可能不需要加密参数有时添加到DB URL。