Django接金仓数据库:我踩过的坑和填坑指南
Django接金仓数据库:我踩过的坑和填坑指南
开头的故事
去年做一个内部管理系统,后端用Django,客户后来要求数据库换成金仓。当时我心里挺没底的——Django的ORM很依赖数据库方言支持,金仓不是Django官方支持的数据库,万一跑不起来怎么办?
查了一圈资料,发现金仓官方提供了Django方言包。赶紧搭了个demo验证,从环境配置到跑通CRUD,前后折腾了小半天。中间踩了几个坑,今天就把过程完整的记下来。
一、准备工作:方言包和驱动
1.1 两个东西都得装
Django要连金仓,需要两样东西:
- ksycopg2:金仓的Python驱动,底层负责和数据库通信
- Kingbase方言包:告诉Django的ORM怎么生成金仓能认的SQL
方言包依赖ksycopg2,所以得先装驱动。另外要注意,ksycopg2对Python版本有要求,我用的是Python 3.8,Django 2.2.14,这个组合官方测试过,比较稳。
1.2 找到Django的安装路径
方言包要放到Django的backends目录下。可以用pip查看Django装在哪:
pip show django输出里找Location那一行,比如:
Location: /usr/local/lib/python3.8/site-packages然后把方言包整个目录拷贝到:
/usr/local/lib/python3.8/site-packages/django/db/backends/kingbase如果backends目录下没有kingbase文件夹,新建一个就行。
1.3 装对ksycopg2版本
方言包是Python写的,不分平台。但ksycopg2不同操作系统版本不一样,Linux用ksycopg2,Windows用ksycopg2-win64。
# Linuxpipinstallksycopg2# Windowspipinstallksycopg2-win64装完之后验证一下能不能导入:
python-c"import ksycopg2; print(ksycopg2.__version__)"不报错就说明驱动装好了。
二、配置数据库连接
2.1 settings.py配置
DATABASES={'default':{'ENGINE':'django.db.backends.kingbase','NAME':'testdb',# 数据库名'USER':'system',# 用户名'PASSWORD':'123456',# 密码'HOST':'192.168.1.100',# IP'PORT':'54321',# 端口'OPTIONS':{'threaded':True,# 多线程环境要开},}}几个关键点:
ENGINE必须写成django.db.backends.kingbase,大小写不能错- 数据库要提前建好,Django不会帮你建
- 如果用连接池或高并发,
threaded: True建议加上
2.2 提前建库
Django不会自动创建数据库,需要手动连上去建:
CREATEDATABASEtestdb;建库的时候注意字符集,Django默认用UTF-8,不配也行。
2.3 用DSN字符串代替HOST和PORT
如果不想分开写HOST和PORT,可以把HOST和PORT都留空,在NAME里写完整连接串:
# 简单格式'NAME':'192.168.1.100:54321/testdb'# 完整DSN格式(连RAC时用)'NAME':'(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.100)(PORT=54321))(CONNECT_DATA=(SERVICE_NAME=testdb)))'三、迁移时的注意事项
3.1 表名和字段名的长度限制
金仓对名称长度限制是30个字符。Django自动生成的表名有时会超,比如appname_very_long_model_name。方言包的处理方式是:超长就截断,最后4个字符用MD5哈希值替换。
如果不想让Django改你的表名,可以用db_table强制指定:
classLongModelName(models.Model):name=models.CharField(max_length=50)classMeta:# 用双引号包起来,Django就不做转换了db_table='"my_custom_table_name"'3.2 字段名避开关键字
Django会自己处理SQL注入问题,但如果字段名刚好是金仓的关键字,还是会翻车。特别要注意这几个:date、timestamp、number、float。
# 容易出问题的写法classEvent(models.Model):date=models.DateField()# date是关键字timestamp=models.DateTimeField()# timestamp也是关键字改成别的名字,或者手动指定db_column:
classEvent(models.Model):event_date=models.DateField(db_column='event_date')3.3 NULL和空字符串踩坑
Django习惯用空字符串''表示空值,但金仓把空字符串和NULL当成两回事。方言包的处理逻辑是:
- 建表时,字段如果允许空字符串,会隐式加上
null=True - 读数据时,如果字段值是NULL,自动转成空字符串
大多数时候不用操心,但如果有业务逻辑依赖区分None和'',在Django里可能不好实现。一个绕过的方式是给字段设个默认值,比如default='unknown'。
3.4 TextField的限制
金仓的TextField在底层是NCLOB类型,这东西有几个限制:
- 不能做主键
- 不能建索引
- 不能用在DISTINCT查询里
如果模型里有TextField,又调用了distinct(),会直接报错。绕过去的方法是查的时候先去掉TextField:
# 会报错User.objects.values('name','bio').distinct()# 先defer掉TextField字段User.objects.defer('bio').values('name').distinct()四、连接池和性能
4.1 持久连接配置
Django默认每次请求结束就关连接。如果并发量上来,频繁开关连接开销不小。可以考虑开启持久连接:
DATABASES={'default':{'ENGINE':'django.db.backends.kingbase','NAME':'testdb','USER':'system','PASSWORD':'123456','HOST':'192.168.1.100','PORT':'54321','CONN_MAX_AGE':600,# 连接保持600秒}}CONN_MAX_AGE按秒算,设成600就是10分钟。如果数据库服务器有空闲连接超时策略,这个值要设得比数据库的小一些。
注意:Django自带开发服务器每个请求新建一个线程,持久连接反而会建很多连接,开发环境别开。
4.2 多线程配置
在生产环境跑Django,比如用uWSGI或Gunicorn,每个worker是一个独立进程。要加上threaded: True:
'OPTIONS':{'threaded':True,}这个参数告诉驱动使用线程安全的连接模式。不加的话,高并发时候可能会崩。
五、代码示例
配置文件没问题之后,Django的ORM用法和连PostgreSQL/MySQL完全一样。下面是一个完整的例子。
models.py:
fromdjango.dbimportmodelsclassUser(models.Model):name=models.CharField(max_length=50,unique=True)age=models.IntegerField()email=models.EmailField()created_at=models.DateTimeField(auto_now_add=True)classMeta:db_table='app_user'# 表名不要超30字符ordering=['-created_at']def__str__(self):returnf"{self.name}({self.age})"执行迁移:
python manage.py makemigrations python manage.py migrate如果迁移时报错说表已存在,检查一下Meta.db_table是不是和已有表重了。
增删改查操作:
# 插入user=User(name='张三',age=25,email='zhangsan@example.com')user.save()# 批量插入User.objects.bulk_create([User(name='李四',age=30,email='lisi@example.com'),User(name='王五',age=28,email='wangwu@example.com'),])# 查询users=User.objects.filter(age__gte=18).order_by('-age')foruinusers:print(f"{u.name}-{u.age}岁")# 更新User.objects.filter(name='张三').update(age=26)# 删除User.objects.filter(age__lt=18).delete()原生SQL(方言包支持):
fromdjango.dbimportconnectionwithconnection.cursor()ascursor:cursor.execute("SELECT * FROM app_user WHERE age > %s",[18])rows=cursor.fetchall()forrowinrows:print(row)六、常见问题排查
驱动装不上
- Linux下报错缺少
pg_config,实际上金仓驱动需要的是ksycopg2,不是psycopg2。确认是不是装错了包名。 - Windows下报编译错误,直接装预编译的
ksycopg2-win64,别从源码编译。
迁移时报“relation already exists”
可能是之前迁移失败留下残留表。手动删了表再重新迁移,或者python manage.py migrate --fake标记为已完成。
连接数太多
持久连接+多线程+高并发,每个线程都会保持一个连接。适当调低CONN_MAX_AGE,或者限制Django Worker数量。
时区问题
Django的USE_TZ=True默认打开。如果金仓数据库存的是无时区时间,查出来会和预期差8小时。要么统一用带时区字段,要么设置USE_TZ = False。
七、总结
金仓对Django的适配做得比较完整,方言包覆盖了大部分常用场景:
- 迁移和ORM基本都能正常工作
- 持多线程和连接池
- 表名、字段名的转换规则比较灵活
- 对金仓特有的类型(如CLOB)做了一层封装
需要注意的地方主要是长度限制、TextFiled的限制、NULL和空字符串的差异。把这些提前了解清楚,基本不会卡住。
如果你的团队用Django做Web开发,数据库要换金仓,不用太担心。找个测试环境先跑一遍,把项目里超长的表名和关键字字段名处理一下,其他大概率能平滑迁移。
