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装在哪:

bash 复制代码
pip show django

输出里找Location那一行,比如:

bash 复制代码
Location: /usr/local/lib/python3.8/site-packages

然后把方言包整个目录拷贝到:

bash 复制代码
/usr/local/lib/python3.8/site-packages/django/db/backends/kingbase

如果backends目录下没有kingbase文件夹,新建一个就行。

1.3 装对ksycopg2版本

方言包是Python写的,不分平台。但ksycopg2不同操作系统版本不一样,Linux用ksycopg2,Windows用ksycopg2-win64

bash 复制代码
# Linux
pip install ksycopg2

# Windows
pip install ksycopg2-win64

装完之后验证一下能不能导入:

python 复制代码
python -c "import ksycopg2; print(ksycopg2.__version__)"

不报错就说明驱动装好了。

二、配置数据库连接

2.1 settings.py配置

python 复制代码
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不会自动创建数据库,需要手动连上去建:

sql 复制代码
CREATE DATABASE testdb;

建库的时候注意字符集,Django默认用UTF-8,不配也行。

2.3 用DSN字符串代替HOST和PORT

如果不想分开写HOST和PORT,可以把HOST和PORT都留空,在NAME里写完整连接串:

python 复制代码
# 简单格式
'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强制指定:

python 复制代码
class LongModelName(models.Model):
    name = models.CharField(max_length=50)
    
    class Meta:
        # 用双引号包起来,Django就不做转换了
        db_table = '"my_custom_table_name"'

3.2 字段名避开关键字

Django会自己处理SQL注入问题,但如果字段名刚好是金仓的关键字,还是会翻车。特别要注意这几个:datetimestampnumberfloat

python 复制代码
# 容易出问题的写法
class Event(models.Model):
    date = models.DateField()        # date是关键字
    timestamp = models.DateTimeField() # timestamp也是关键字

改成别的名字,或者手动指定db_column

python 复制代码
class Event(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:

python 复制代码
# 会报错
User.objects.values('name', 'bio').distinct()

# 先defer掉TextField字段
User.objects.defer('bio').values('name').distinct()

四、连接池和性能

4.1 持久连接配置

Django默认每次请求结束就关连接。如果并发量上来,频繁开关连接开销不小。可以考虑开启持久连接:

python 复制代码
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

python 复制代码
'OPTIONS': {
    'threaded': True,
}

这个参数告诉驱动使用线程安全的连接模式。不加的话,高并发时候可能会崩。

五、代码示例

配置文件没问题之后,Django的ORM用法和连PostgreSQL/MySQL完全一样。下面是一个完整的例子。

models.py

python 复制代码
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=50, unique=True)
    age = models.IntegerField()
    email = models.EmailField()
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'app_user'  # 表名不要超30字符
        ordering = ['-created_at']
    
    def __str__(self):
        return f"{self.name}({self.age})"

执行迁移:

bash 复制代码
python manage.py makemigrations
python manage.py migrate

如果迁移时报错说表已存在,检查一下Meta.db_table是不是和已有表重了。

增删改查操作:

python 复制代码
# 插入
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')
for u in users:
    print(f"{u.name} - {u.age}岁")

# 更新
User.objects.filter(name='张三').update(age=26)

# 删除
User.objects.filter(age__lt=18).delete()

原生SQL(方言包支持):

python 复制代码
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM app_user WHERE age > %s", [18])
    rows = cursor.fetchall()
    for row in rows:
        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开发,数据库要换金仓,不用太担心。找个测试环境先跑一遍,把项目里超长的表名和关键字字段名处理一下,其他大概率能平滑迁移。

相关推荐
_风满楼1 小时前
HTTP 请求的五种传参方式
前端·javascript·后端
码事漫谈2 小时前
为什么 token 计费规则里,输出比输入贵那么多
后端
Go_error2 小时前
Go database/sql 基于临时 channel 传递连接
后端·go
Go_error2 小时前
Go 循环栅栏
后端·go
程序员老邢2 小时前
【产品底稿 07】商助慧 Admin 运维模块落地:从 “能跑” 到 “能运维”,3 个页面搞定日常排障
java·运维·经验分享·spring boot·后端
彩票管理中心秘书长2 小时前
npm 依赖管理机制完全解析(超详细版)
后端
彩票管理中心秘书长2 小时前
npm 脚本与自动化完全指南(超详细版)
后端
元宝骑士2 小时前
Spring @Async 异步无法获取当前登录用户?Sa-Token 1.34.0 终极踩坑解决方案
java·后端
鱼人2 小时前
Fibers(纤程)来了:打破阻塞,实现纯PHP下的异步非阻塞IO
后端