扩展和优化数据库以满足你的应用程序的需求可能是一个重大挑战。如果你还没有读过我最近关于Django如何为Python 和SQL数据库应用程序完成重任的博客,我强烈建议你去看看。但是TL;DR版本是,SQL是为SQL数据库优化的,Python 不是,而Django是一个很好的中介,可以帮助你建立更有效的应用程序,在一起使用这两种语言时,可以减少摩擦、复杂性和代码。
因此,虽然Django完成了创建数据库应用的重任,但你仍然要负责数据库的日常管理和监控。其中一些管理任务可以推迟到你的云提供商那里,使用Linode Managed Databases这样的服务,但你可能会在扩展时发现新的路障,比如说:
- 数据库迁移。通过对数据库方案的控制性改变,将现有的数据库转换到一个新的、理想的状态。
- 多数据库部署。为了优化性能,开发人员可以设计他们的应用程序,为分段功能使用单独的数据库。例如,一个主要的读/写数据库和一个用于普通查询的读副本数据库。
如果你的一个数据库使用SQL,你可以使用Django来减少摩擦,在处理大量数据的同时让你的生活变得更加轻松。
这个关于两个关键数据库管理概念的介绍与《理解数据库》电子书和我的新教育视频系列中关于构建一个可生产的Django应用程序的逐步说明相匹配。无论哪种学习途径,都能帮助你让Django为你完成SQL重任。
数据库迁移
当你开始工作时,为任何给定的列确定正确的数据类型可能有点棘手,特别是你的数据需求将不可避免地随着时间的推移而改变。如果你希望你的标题字段只有80个字符长怎么办?如果你需要添加一个时间戳字段,以便你能准确地跟踪项目被添加到数据库的时间呢?
由于一些原因,在创建表格后改变它可能会变得相当混乱:
- 你如何处理预先存在的价值观?
- 如果预先存在的行缺少新列/字段的数据怎么办?
- 如果你删除一个列/字段呢?数据会发生什么变化?
- 如果你添加了一个以前不存在的关系(即外键)怎么办?
对于Django开发者来说,幸运的是,我们有一个叫做 makemigrations
和 migrate
.
让我们来看看它是如何运作的。
下面是我们的Django数据模型的例子:
class BlogArticle(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.SET_DEFAULT)
title = models.CharField(max_length=120)
slug = models.SlugField(blank=True, null=True)
content = models.TextField(blank=True, null=True)
publish_timestamp = models.DateTimeField(
auto_now_add=False,
auto_now=False,
blank=True,
null=True,
)
让我们来添加这个字段:
updated_by = models.ForeignKey(
User, related_name="editor", null=True, blank=True, on_delete=models.SET_NULL
)
这个字段将使我们能够跟踪最后一个对我们的模型进行修改的用户。
让我们更新一下我们的模型:
class BlogArticle(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.SET_DEFAULT)
title = models.CharField(max_length=120)
slug = models.SlugField(blank=True, null=True)
content = models.TextField(blank=True, null=True)
publish_timestamp = models.DateTimeField(
auto_now_add=False,
auto_now=False,
blank=True,
null=True,
)
# our new field
updated_by = models.ForeignKey(
User, related_name="editor", null=True, blank=True, on_delete=models.SET_NULL
)
现在,在我们保存这个文件后,这个 BlogArticle
类被声明在(models.py
),我们如何让我们的数据库知道这个变化的发生?
有两种方法:
python manage.py makemigrations
python manage.py migrate
让我们讨论一下这两个命令的作用:
python manage.py makemigrations
python manage.py makemigrations
寻找以下方面的变化 所有 models.py
文件,并寻找其中的变化。如果发现有变化,就会创建一个新的python 文件,其内容为 拟议的变化 我们的SQL数据库 需要 要做的。拟议的变化看起来像这样:
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('articles', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='article',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='editor', to=settings.AUTH_USER_MODEL),
),
]
当然,这只是另一个Python 文件。这个文件让我们(开发者)知道在我们的数据库中应该发生什么。它是用Python ,而不是SQL写的,以保持凝聚力并利用Django ORM的内置功能。
但为什么这是一个应该发生的文件呢?嗯,这有几个原因:
- 如果我们需要在它发生之前审查应该发生的事情,我们可以在这里抓住它。
- 这
makemigrations
命令不 查阅 数据库,看看这一变化是否 可以 甚至发生。 - 数据库可能已经被改变以适应这些要求(取决于与谁/什么人管理数据库有关的一些因素)。
- 如果我们需要在改变生产数据库之前运行测试,现在将是一个很好的时机。
假设这个改动是有效的(就我们所知),我们可以提交这些改动:
python manage.py migrate
python manage.py migrate
试图为我们改变数据库--所有的字段、列、表、外键,你可以说出来--Django将为我们做这些工作,帮助确保数据库以我们预期的方式更新。
需要注意的是,Django可能会因为一些原因而无法进行这些修改。对于新的Django开发人员来说,这几乎总是由于添加和删除字段和列,以及未能正确运行迁移。
如果做得正确、 python manage.py migrate
确保了一个稳定的系统,将我们的Python 代码与我们的SQL表相匹配,从而使我们能够获得Django和SQL数据库所提供的所有精彩内容。
这如何给我们带来更多的灵活性?
Python 具有广泛的应用,而SQL却没有。结构化查询语言的名称中写着它的局限性。谁在用SQL创建皮克斯的动画?
好了,所有这些都是说,Python 的简单性实际上有助于开发人员采用SQL的力量,而且可能是在不知不觉中。
为什么管理数据库和Django有意义
当谈到创建一个网络应用,包括Django,你需要决定一些事情:
- 我们想要哪种数据存储解决方案?MySQL、Postgres、MongoDB、Redis、对象存储,等等。
- 我们将如何运行/与数据存储解决方案整合?
- 我们将如何从中断或停机中恢复?
- 我们将如何维护存储解决方案?
- 我们将如何保障我们的存储解决方案?
- 我们将如何备份我们的存储解决方案?
这些问题的答案可能会随着你的项目复杂性的增加而改变,但它们都是从同一个地方开始的:在自我管理和第三方管理之间做出决定。
自我管理:
- 优点:控制和成本。
- (重要) 康:你要对一切负责。
管理服务往往从一开始就花费更多的钱,而自我管理意味着你可以使用你喜欢的Linux发行版,并以某种方式(或某种程度)为你的需要进行优化。这可以包括运行你的团队修改过的MySQL的分叉版本。你可能在运行你的服务上节省美元,但这总是需要更多的时间来维护。
第三方管理的数据库:
是的,以美元和美分计算,它可能略微贵一些,但维护的时间会大大减少。这个选项和管理数据存储解决方案是我的网络应用的事实选择。在这个例子中,我们已经在利用Django来管理数据库事务。SQLAlchemy也有这个优势,因为它与FastAPI、Flask等框架一起使用。如果你已经把你的SQL编写工作外包给了一个Python 包,为什么不把你的SQL服务器运行工作外包出去呢?
现在,鉴于Python ORM(如Django ORM和SQLAlchemy)的有效性,我建议你尽可能地使用托管数据库和/或托管数据存储服务,以下是你这样做的好处:
- 缩短了开发时间
- 减少了管理时间
- 缩短了恢复时间
- 减少了服务中断
- 减少了部署和开发的复杂性
- 降低数据库迁移的复杂性(从其他服务)。
- 减少了SQL开发人员的重复/无效/低效的活动
- 降低DevOps/Ops的复杂性
- 提高非SQL开发人员的效率
- 提高部署和开发速度
- 增加可靠性(通常由服务水平协议支持)。
- 提高安全性
- 提高可维护性
- 在备份和冗余方面有所增加
- 费用的边际增加
我在制定上述清单时,考虑到了在Linode上使用管理型MySQL数据库集群以及Linode对象存储(用于存储CSS、JavaScript、图片、视频等文件)的思路。实际上,使用这些服务可以帮助我们保持专注于用Django、FastAPI、Flask、Node.js 或其他什么东西构建一个优秀的网络应用。换句话说,我们将重点转移到构建你的用户真正需要的工具和软件上。你知道,对他们来说,真正的价值在哪里。
MySQL、PostgreSQL、Redis和Django
在很长一段时间里,Django的主要数据库是PostgreSQL。我认为,这在很大程度上是由于你只能在Postgres中使用JSONField的事实。随着Django 3.2+和MySQL 5.7.8+的推出,JSONField现在也可以用于MySQL了。
为什么这很重要?
在处理用户生成的内容或存储来自其他API 服务的数据时,经常需要存储非结构化数据,如JSON。让我们来看看如何:
from django.db import models
class Pet(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)
def __str__(self):
return self.name
下面是我想存储的与这个Pet有关的数据:
pet1 = {
"name": "Bruno",
"type": "Rat",
"nickname": "We don't talk about it",
"age": 2,
"age_interval": "months"
}
pet2 = {
"name": "Tom",
"type": "Cat",
"breed": "Mixed"
"age": 4,
"age_interval: "years",
"favorite_food": [{"brand": "Acme", "flavor": "Tuna" }]
}
pet3 = {
"name": "Stewey",
"type": "Dog",
"breed": "unknown"
"age": 34,
"age_interval: "dog years",
"nickname": "Football"
}
这些数据告诉我们,什么时候我们可能需要一个 JSONField
.我们可以存储所有的宠物名称(使用 name
键),其余的保存在JSONField中。最酷的事情是 JSONFields
是它们可以像其他标准的Django字段一样被查询,即使有这些不同的模式。
在Django开发者中,关于使用哪种数据库的争论一直存在:MySQL还是PostgreSQL。在很长一段时间里,我总是选择PostgreSQL,因为JSONField只在PostgreSQL上可用,而现在已经不是这样了。我说选择一个并坚持下去,直到它不再满足你的需要。
但我们用Redis做什么呢?
Redis是一个内存数据存储,速度快得惊人,经常被用作临时数据库(稍后详述)、缓存服务和/或消息传递队列。我称它为临时数据库的原因是它是内存的。内存通常比磁盘存储更昂贵,因此将数据长期存储在内存中通常是不可行的。
我对Redis和Django的主要使用情况是缓存和排队。
缓存:比方说,你有几个用户经常访问的网页。你希望这些页面中的数据能尽快地显示给用户。Redis作为Django的一个缓存系统,让这一切变得异常简单。这些页面中的数据可能是从一个SQL数据库中渲染出来的,但Redis可以从缓存中存储这些渲染出来的数据。换句话说,将Redis与SQL一起使用往往可以加快你的响应速度,同时减少对SQL数据库的查询量。
排队:Redis的另一个流行的用例是将长期运行的任务卸载到另一个进程(通常通过一个叫做Celery的Python 包)。当你需要这样做时,你可以使用Redis作为一个队列,将应该在另一个时间完成的任务放在队列中。
例如,如果你有一个用户需要一份过去五年所有交易的报告,软件可能需要几个小时才能真正生成这份报告。显然,没有人会在一台机器上盯着看几个小时。因此,我们会把这个请求从用户那里卸载到Redis队列中。一旦进入Redis,我们就可以让一个工作进程运行(就像在Django中使用Celery)来实际生成报告。一旦报告完成,不管花了多长时间,用户都会得到通知。这种通知,和其他通知一样,也可以通过Redis队列加上Celery/Django工作进程来完成。
这都是说,Redis和MySQL实际上是非常互补的。你可以通过Linode Marketplace部署一个自我管理的Redis数据库服务器。
对象存储
我推荐使用的最后一个与数据相关的管理服务是Linode对象存储。对象存储负责所有你可能需要存储的其他种类的数据。例如,我们不会在MySQL中存储一个视频的所有字节。相反,我们会存储与该视频相关的元数据,并将视频存储在对象存储中。
下面是一些你会使用对象存储的事情:
- 层叠样式表(CSS)
- JavaScript(如React.js、Vue.js、Vanilla.js等)。
- 视频
- 图片(原始的和压缩的)
- CSVs, XLSX
- 数据库备份
- Docker容器镜像层(如果是自我管理)。
- 训练过的机器学习算法的迭代
- Terraform 国家档案
- PDF(包括大型和小型)。
- 任何需要经常下载(或上传)的持久性文件
摘要
读完这篇文章后,我希望你有动力在你的Web应用项目中利用管理服务的力量。Django是在SQL数据库之上构建Web应用的一个优秀解决方案,但它肯定不是唯一的解决方案。如果你想深入了解SQL和SQL服务器的内部情况,我想这是一个值得的练习,看看有多少成功的应用程序利用Django来处理Django能做的大部分事情。
这里有几个(或许多)亮点,使Linode上的Django与管理的MySQL变得非常棒:
- Django为你做繁重的SQL工作(Flask/FastAPI的SQLAlchemy等工具也是如此)。
- Django也能实现原始的SQL命令(同样,像SQLAlchemy这样的工具也是如此)。
- Django帮助初学者学习SQL命令
- Django内置了对MySQL和PostgreSQL的支持(除了一个针对db的python 客户端)。
- 提高了生产部署的速度
- 提高可靠性和可恢复性
- 使得开发和生产环境能够几乎完全匹配数据库技术
- 使得基于容器的Django更容易、更可靠
- 解锁从单节点部署到多节点的扩展,甚至全面过渡到Kubernetes。
- 新的Django/Python 开发人员更容易使用生产级系统
- 在多个基于python 的应用程序之间共享数据库更容易、更安全(例如一个FastAPI应用程序从/到基于Django的MySQL数据库读/写)。
- Django的JSONField现在支持使用MySQL(以前只有PostgreSQL)。
- 易于测试(在CI/CD期间或本地开发环境中)。
- 满足Django需求的规模
- 支持在一个Django项目中使用多个数据库,例如:使用MySQL作为主要的读写数据库,以及一个MySQL读复制数据库用于普通查询。
- 严格的访问控制(Linode私有IP,本地开发)。
- 需要SSL证书进行连接(增加了部署的复杂性,但也增加了安全性)。
- 启用私人连接(在同一地区;降低连接成本)。
如果你对在Linode上部署Django应用程序的现代方法以及管理的MySQL数据库、用于CI/CD的GitHub行动、Terraform ,以及Ansible ,请跳入大量免费的逐步教育内容:
- 在Linode上观看我们的理解数据库视频教程系列直播
- 下载《了解数据库》:扩展版》电子书
为了帮助你开始,Coding for Entrepreneurs GitHub有一个代码库,与该系列的每个步骤相配套。祝你好运,一定要通过Twitter@JustinMitchel让我知道事情进展如何。
注释