尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Django连接MySQL全配置指南:从驱动选型到生产避坑

Django连接MySQL全配置指南:从驱动选型到生产避坑 Django项目开发到一定阶段换数据库是迟早的事。默认的SQLite在本地调试很方便但一旦涉及并发写入、数据量增长、多实例部署MySQL往往才是更稳妥的选择。这篇文章就把Django连接MySQL的完整配置过程拆开讲透从驱动选型、settings.py参数含义到建库授权、迁移验证再到生产环境里的坑和优化一条龙说清楚。我自己这些年接过不少半路切换数据库的项目也见过太多人卡在“配置写好了但连不上”“中文乱码”“迁移报错”这类问题上。其实大部分原因都是对连接链路的几个关键点理解不透。这篇文章适合刚接触Django不久、想从SQLite切到MySQL的开发者也适合已经连上但出了各种诡异问题的朋友对照排查。我会尽量把每一步背后的原理也讲明白而不是只给你一段能跑的配置。1. 动手前先把思路理顺1.1 Django默认用SQLite为什么要换成MySQLDjango新建项目时默认配置就是SQLite原因很简单零配置、单文件、开箱即用。本地开发、写Demo、跑单元测试SQLite完全够用。但你要想清楚自己的项目将来会怎么跑。SQLite的写锁是库级别的也就是说同一时刻只能有一个连接在写数据。并发稍微上来一点就会出现database is locked。另外SQLite本身是嵌入式数据库不太适合作为独立服务被多个应用实例共享。如果你打算部署多台应用服务器或者要让数据独立于应用节点存在MySQL几乎是必然选项。MySQL作为独立的数据库服务支持多用户并发访问InnoDB引擎默认行级锁写并发能力远强于SQLite。再加上主从复制、定时备份、慢查询日志这些生态工具生产环境里维护起来要成熟得多。所以切换数据库不是“炫技”而是项目从原型走向真实服务的第一步。1.2 驱动怎么选mysqlclient还是PyMySQLDjango连接MySQL底层需要一个Python数据库驱动。最常用的两个是mysqlclient和PyMySQL。mysqlclient是MySQL官方C客户端库的Python封装性能好Django官方文档也推荐。但它依赖系统级的编译环境装起来有时候会卡壳尤其Windows上经常因为缺Visual C编译器和MySQL库文件导致安装失败。PyMySQL是纯Python实现的驱动优点就是安装简单pip install pymysql就能用兼容性好。缺点是在极端高并发场景下性能相比C扩展驱动会弱一些但对绝大多数业务系统来说这个差距其实感知不明显。我的建议是能用mysqlclient就用mysqlclient装不上再退而求其次用PyMySQL。如果你用的是PyMySQL还要在项目入口处加一行兼容声明老版本Django需要这样处理import pymysql pymysql.version_info (1, 4, 6, final, 0) pymysql.install_as_MySQLdb()如果是Django 4.x配合PyMySQL官方已经原生支持不需要额外声明兼容。但为了保险起见很多人仍然会保留这行代码。使用mysqlclient的话则什么都不用加。2. 核心配置项逐项拆解2.1 DATABASES字典的完整结构Django的数据库配置集中在settings.py的DATABASES字典里。一段最基本的MySQL连接配置长这样DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: myapp_db, USER: myapp_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }这段配置里ENGINE固定写django.db.backends.mysql告诉Django当前使用的是MySQL数据库。NAME是数据库名不是表名也不是用户名。USER和PASSWORD是连接MySQL时用的账号密码。HOST是数据库服务所在地址本机写127.0.0.1远程写IP或域名。PORT默认3306如果你的MySQL改了端口这里要同步改。很多人会把NAME误写成数据库连接串或者把HOST写成mysql://开头的URL这些都是常见的低级错误。Django的DATABASES字典不接受连接字符串它只接受拆好的字段。如果你习惯用URL风格可以装dj-database-url这个第三方库来做转换但原生配置里必须拆开写。2.2 HOST、PORT、OPTIONS这些参数到底怎么填HOST和PORT没什么难度真正容易踩坑的是OPTIONS。比如连接MySQL时如果服务器要求SSL或者你想指定字符集、设置超时时间都需要用到OPTIONSDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: myapp_db, USER: myapp_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, connect_timeout: 5, }, } }OPTIONS里的参数会直接传给数据库驱动。charset指定连接字符集init_command会在每次连接建立后执行指定的SQLconnect_timeout设置连接超时时间秒。在生产环境里connect_timeout特别重要如果数据库故障应用不至于长时间卡在建立连接上。还有一个不常见但很有用的配置是isolation_level: READ COMMITTED。MySQL默认的事务隔离级别是REPEATABLE READ但有些业务场景下READ COMMITTED能减少锁竞争和间隙锁问题。Django里可以通过OPTIONS直接指定隔离级别省得每次手动SET。2.3 字符集、时区和事务隔离级别字符集是最容易被忽视的配置。MySQL默认的latin1字符集不支持emoji和一些生僻汉字现在主流方案是utf8mb4。你需要确保三处字符集一致数据库本身的字符集、数据表的字符集、Django连接时指定的字符集。建库时你可以指定CREATE DATABASE myapp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Django连接侧对应设置OPTIONS: { charset: utf8mb4, }另外Django的USE_TZ默认是True也就是说Django会把datetime统一转成UTC存入数据库渲染时再转回本地时区。MySQL连接时最好在OPTIONS里加上init_command: SET time_zone 08:00或者直接在MySQL全局设置time_zone否则容易出现时间差8小时的问题。事务隔离级别方面MySQL默认REPEATABLE READ对大多数业务没问题但如果你在高并发下遇到死锁可以尝试改成READ COMMITTED。修改方式在OPTIONS里加isolation_level: READ COMMITTED。3. 从零到一完整连接MySQL实操记录3.1 先建库建账号权限别给错连接MySQL之前先在数据库侧准备好库和账号。这步看着简单但权限给错会引发后续一连串问题。我一般这么做CREATE DATABASE myapp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER myapp_userlocalhost IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON myapp_db.* TO myapp_userlocalhost; FLUSH PRIVILEGES;如果Django应用和MySQL不在同一台机器host部分就要写应用服务器的IP或使用通配符。为了安全不建议直接给myapp_user%而是明确指定IP段。权限也要细粒度通常只需要SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX这类业务所需权限Django迁移时还需要REFERENCES权限。如果项目使用Django自带的migration机制建议直接授予业务库的全部权限省得以后增加功能迁移时报权限不足。3.2 安装驱动并修改配置文件建好库之后先安装驱动。以mysqlclient为例pip install mysqlclient如果报错Windows用户可以直接下载对应Python版本的whl文件安装或者干脆换成PyMySQLpip install pymysql然后在settings.py里修改DATABASES配置。这里我放一份完整的生产级示例DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: myapp_db, USER: myapp_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, connect_timeout: 5, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }这里我设置了STRICT_TRANS_TABLES这个sql_mode它会在数据写入不符合表结构要求时报错而不是静默截断。开发阶段开这个模式能提前暴露数据问题省得生产环境出现脏数据。3.3 用迁移命令验证整个链路配置完成后不要急着写业务代码先跑一下迁移命令来验证连接链路是否通畅。python manage.py migrate如果一切正常你会看到一串Apply all migrations的输出。这证明Django能连上MySQL、能创建数据表、能正常读写数据。同时我建议跑一下Django自带的环境检查python manage.py check然后启动开发服务器进入Django shell测试常用模型python manage.py shell在shell里执行from django.db import connection然后执行connection.cursor().execute(SELECT VERSION())如果能取到MySQL版本号说明整个调用链完全打通。3.4 连接池和长连接参数怎么调Django默认每次请求都新建数据库连接请求结束关闭连接。高并发下频繁建连会浪费大量时间这时候可以考虑连接池。mysqlclient支持MySQLdb的连接池也可以用独立的连接池库比如django-db-connection-pool或者直接在配置层面把CONN_MAX_AGE调大。CONN_MAX_AGE是Django内置参数表示连接复用时间单位秒。比如设置DATABASES { default: { # 省略其他配置 CONN_MAX_AGE: 60, } }这个参数的意思是连接创建后60秒内可以复用超过60秒才重新建立连接。它能明显减少握手开销适合大多数Web应用。但要注意如果连接池里的连接在MySQL端已经被切断而复用连接没有做好探活会出现“MySQL server has gone away”错误。所以使用CONN_MAX_AGE时最好配合OPTIONS里的connect_timeout来缩短等待时间。4. 连接过程中最常见的坑与排查实录4.1 mysqlclient装不上怎么办这是出镜率最高的问题。Linux上通常需要安装python3-dev、default-libmysqlclient-dev这几个系统包Windows上需要Visual C构建工具。如果系统环境不方便折腾最快方案就是换PyMySQL。PyMySQL纯Python实现不存在编译问题装上再在manage.py里声明兼容即可。如果你执意要用mysqlclient可以尝试从预编译包安装。Windows上访问第三方提供的wheel仓库下载对应Python版本的mysqlclient文件然后pip install 本地文件。4.2 authentication plugin密码认证失败新版MySQL默认认证插件是caching_sha2_password老版本是mysql_native_password。如果Django驱动版本比较旧连接时会报Authentication plugin caching_sha2_password cannot be loaded。解决方法是升级驱动到支持caching_sha2_password的版本。mysqlclient 1.4.6以上基本没问题PyMySQL 1.0.0以上也没问题。如果你因为某些原因必须用旧驱动可以把账号认证插件指定为mysql_native_passwordALTER USER myapp_userlocalhost IDENTIFIED WITH mysql_native_password BY your_password;不过这只适合临时过渡长远来看还是升级驱动更靠谱。4.3 中文乱码问题乱码基本逃不出三个原因数据库字符集不是utf8mb4、连接字符集不是utf8mb4、数据表字符集不是utf8mb4。排查顺序是先看库再看表最后看连接SHOW CREATE DATABASE myapp_db; SHOW CREATE TABLE myapp_db.some_table;如果表还是latin1执行ALTER TABLE some_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;如果库和表都没问题那就是连接层charset没设对回头在DATABASES的OPTIONS里补上charset: utf8mb4重启Django再试。4.4 Connection refused和超时Cant connect to MySQL server这类报错大概率是网络不通、端口没开、或者MySQL没在监听。先用命令行客户端测试一下mysql -h 127.0.0.1 -P 3306 -u myapp_user -p如果命令行能连上但Django连不上检查HOST是否写成了localhost。localhost可能会走socket连接而Django驱动走TCP两者行为不同。建议统一写成127.0.0.1。如果连接一直超时检查防火墙、安全组、MySQL的bind-address配置。MySQL默认可能只监听127.0.0.1需要修改my.cnf里的bind-address为0.0.0.0或指定IP然后重启MySQL。4.5 迁移时提示权限不足运行migrate时报Access denied说明账号权限不够。Django迁移需要建表、改表、删表、建索引这些DDL权限。最简单的调整是在MySQL侧执行GRANT ALL PRIVILEGES ON myapp_db.* TO myapp_userlocalhost; FLUSH PRIVILEGES;如果你希望权限收敛一点至少需要CREATE、ALTER、DROP、INDEX、REFERENCES这五个DDL权限加上增删改查和数据表的SELECT权限。5. 生产环境里我反复强调的几件事5.1 不要把密码写在代码里开发阶段为了方便密码直接写在settings.py里没问题。但生产环境千万别这么干。配置文件和代码一旦进了版本仓库密码就等于泄露了。我习惯用环境变量管理敏感信息import os DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: os.environ.get(DB_NAME), USER: os.environ.get(DB_USER), PASSWORD: os.environ.get(DB_PASSWORD), HOST: os.environ.get(DB_HOST, 127.0.0.1), PORT: os.environ.get(DB_PORT, 3306), } }配合django-environ或python-dotenv本地开发放.env文件生产环境由部署平台注入环境变量这样既灵活又安全。5.2 多环境配置与多数据库路由项目到后期会区分开发、测试、生产多个环境。可以用django-environ按环境加载不同配置也可以拆分成settings_base.py、settings_dev.py、settings_prod.py。如果项目涉及多个数据库比如一个业务库一个日志库Django支持配置多个连接DATABASES { default: { # 业务库 }, logs: { # 日志库 } }配合DATABASE_ROUTERS可以按模型类区分读写到哪个库。但我的建议是非必要不引入多库。多库会带来事务跨库、迁移顺序、备份恢复等一系列复杂性小项目单库足够。5.3 连接数、超时与监控Django配置里的CONN_MAX_AGE会影响应用侧连接数MySQL的max_connections影响数据库侧上限。两者要配合调整。比如你的应用有10个进程每个进程池化20个连接那MySQL的max_connections至少要留出200以上的余量否则高峰期会直接拒绝连接。MySQL侧查看当前连接数SHOW STATUS LIKE Threads_connected;设置最大连接数SET GLOBAL max_connections 300;但注意这个参数重启后失效生产环境要去my.cnf里修改。除此之外建议开启慢查询日志定位那些执行时间过长的SQL。Django自身的日志也可以把SQL输出出来调试LOGGING { version: 1, handlers: { console: { class: logging.StreamHandler, }, }, loggers: { django.db.backends: { handlers: [console], level: DEBUG, }, }, }Django连接MySQL这件事看起来就是改几行配置实际跑通之后你会碰到各种环境差异、驱动差异、字符集和权限问题。但底层原理是固定的建库、建账号、配驱动、写DATABASES、跑migration链路只要通了后续开发就和SQLite时代没太大区别。我个人在做项目时习惯先搭好一套标准配置模板把utf8mb4、connect_timeout、CONN_MAX_AGE都写进去然后复制到不同项目里改名字。新项目初始化阶段就切换到MySQL避免后期数据迁移的麻烦。最后再分享一个小技巧如果你在排查问题时实在找不到原因关掉Django的DEBUG页直接看MySQL的general_log日志它会记录每一条到达数据库的请求很多诡异问题在那一层都会现出原形。
返回列表