Django 数据库管理
Django数据库支持
Django支持连接多种数据库,除了默认的SQLite外,还包括MySQL、PostgreSQL和Oracle这些关系型数据库。
##注意事项
持久链接
在setting
中设置CONN_MAX_AGE
参数可以设置数据库连接保持的时间(秒),默认为0:每次请求(request)后关闭与数据库的链接;设置为None时:无限制的持续连接。
连接管理
Django在首次进行数据库查询时会打开与数据库的连接。它保持此连接打开并在后续请求中重用它。 Django一旦超过CONN_MAX_AGE定义的最时间或者不再可用,就会关闭连接。
在每个请求开始时,如果连接达到其最大时间,Django就会关闭连接。如果要设置数据库在一段时间后终止空闲连接,则应将CONN_MAX_AGE设置为一个较低的值,以便Django不会尝试使用已被数据库服务器终止的连接。 (此问题可能只会影响非常低流量的网站。)
在每个请求结束时,Django会关闭那些连接时间达到max_age或者处于不可恢复的错误状态的连接。如果在处理请求时发生任何数据库错误,Django会检查连接是否仍然有效,如果没有则关闭它。因此,数据库错误最多只影响一个请求;如果连接变得不可用,则下一个请求将获得新连接。
警告
由于每个线程都维护自己的连接,因此数据库必须支持至少与工作线程一样多的连接。
有时候,大多数视图都不会访问数据库,例如,因为它是外部系统的数据库,或者使用了缓存机制。这种情况下,需要将CONN_MAX_AGE设置为低值甚至为0,因为维护不太可能被重用的连接没有意义。这将有助于保持同时与数据库进行连接的数量。
开发服务器为它处理的每个请求创建一个新线程,会影响持久连接。在开发过程中不要启用它们。
当Django建立与数据库的连接时,它会根据所使用的后端设置适当的参数。如果启用持久连接,则不会再对每个请求重复此设置。如果修改连接的隔离级别或时区等参数,则应在每个请求结束时恢复Django的默认设置,在每个请求开始时强制使用适当的值,或者禁用持久连接。
编码
Django会默认所有的数据库都使用utf-8
编码,如果使用了其他编码方式可能会导致意料之外的错误。
Django多数据库联用
大多数情况下,Django只需要使用一个数据库。但也可以指定同时使用多个数据库。
连接MySQL数据库
先试使用MySQL数据库代替默认的SQLite数据库,确保安装了MySQL。
- 下载
pymysql
库:pip install pymsql
; - 在管理目录下的__init__.py中添加:
import pymysql
pymysql.install_as_MySQLdb()
让Django将pymysql当做MySQLdb使用
- 修改setting下的
DATABASE
设置,填写MySQL的用户、密码和要使用的数据库,使用非root
用户还需指明host
和port
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'ex1',
'USER': password.dbuser,
'PASSWORD': password.mysql_passwd,
'HOST':'localhost',
'PORT':'3306',
},
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# },
- 执行
migrate
命令
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, djcelery, learning_logs, sessions, sites, users
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
MySQL数据库中就会有数据表。
使用PostgreSQL
需要安装psycopg2:pip install psycopg2
或pip install psycopg2-binary
。不建议几个不同的数据库一起使用。
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# },
'default':{
},
'learning_logs':{
'ENGINE':'django.db.backends.mysql',
'NAME': 'learning_logs',
'PASSWORD':password.mysql_passwd,
'USER':password.dbuser,
'HOST':'localhost',
'PORT':'3306',
},
'users':{
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'users',
'PASSWORD': password.postgres_passwd,
'USER': password.dbuser
'HOST': 'localhost',
'PORT': '5432',
}
}
DATABASE_ROUTERS = []
指定数据库进行操作
可以在QuerySet“链”中的任何位置为QuerySet选择数据库。只需在QuerySet上调用using()
以获取另一个使用指定数据库的QuerySet。
# 查询
Topic.objects.using('learning_logs').all()
UserProfile.objects.using('users').all()
# 保存 或 删除
profile.save(using='users')
profile.delete(using='users')
可以设置数据库自动路由,这样就不需要使用using
数据库自动路由
Django要求保留DATABASE
内的default
条目,但可以没有字典表参数。为此,必须为所有应用程序的模型设置DATABASE_ROUTERS
,包括正在使用的任何贡献和第三方应用程序中的模型,以便不会将任何查询连接到默认数据库。这里将user和learning_logs两个应用下的数据分别存储在2个数据库中。DATABASE_APP_MAPPING
是项目应用与数据库的映射。auth
下模型(User,Group,Permission)依赖于ContentType
,因此2这必须在同一个数据库。 admin
依赖于auth
,因此其模型必须与auth位于同一个数据库中。在给定合适的路由器的情况下,contenttypes.ContentType
,sessions.Session
和sites.Site
中的每一个都可以存储在任何数据库中,但这并没有意义。
# multi-database
# 'path.to.Router'
DATABASE_ROUTERS = ['django_ulysses.database_router.DatabaseAppRouter']
DATABASE_APP_MAPPING = {
# 'app_name' : 'database_name'
'learning_logs': 'learning_logs',
'users': 'users',
'auth': 'users',
'admin': 'users',
'contenttypes': 'users',
'sessions': 'users',
}
同时使用多数据库的最简单方法就是设置数据库路由方案
。通过路由来包证每一条查询都会找到正确的数据库。Django有默认的路由方案,无需执行任何操作,可以自定义一个路由方案实现自己的数据库分配。
数据库路由是一个类,它需要提供这几个方法:db_for_read
指定读取的数据库; db_for_write
指定写入的数据库;allow_relation
确定两个对象之间是否应该允许关系; allow_migrate
是否允许迁移操作。
创建一个 database_router.py 存放数据库路由方案
from django.conf import settings
DATABASE_MAPPING = settings.DATABASE_APP_MAPPING
class DatabaseAppRouter(object):
"""
A router to control all database operations on models for different
databases
In case an app is not set in settings.DATABASE_APPS_MAPPING, the router
will fallback to the `default` database.
Settings example:
DATABASE_APPS_MAPPING = {'app1': 'db1', 'app2': 'db2'}
"""
def db_for_read(self, model, **hints):
""""Point all read operations to the specific database."""
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def db_for_write(self, model, **hints):
"""Point all write operations to the specific database."""
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def allow_relation(self, obj1, obj2, **hints):
"""Allow any relation between apps that use the same database."""
db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label)
db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label)
if db_obj1 and db_obj2:
if db_obj1 == db_obj2:
return True
else:
return False
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""Make sure that apps only appear in the related database."""
print(db, app_label, model_name, hints)
if db in DATABASE_MAPPING.values():
return DATABASE_MAPPING.get(app_label) == db
elif app_label in DATABASE_MAPPING:
return False
return None
**hints
提示信息,可用于确定哪个数据库应接收给定请求。目前,唯一提供的提示是instance,一个与正在进行的读或写操作相关的对象实例。路由器检查是否存在实例提示,并确定是否应使用该提示来更改路由行为。
在models.py文件中的模型类中添加:
class Meta:
app_label = "users"
多个数据库联用时数据导入导出
需要使用--database=[dbname]
(settings.DATABASES
中的数据库名)指定选用的数据库,否则会使用default
。
- 数据迁移、创建数据库:
python manage.py migrate [appname] --database=[dbname]
- 数据导出:
python manage.py dumpdata app1 --database=db1 > app1_fixture.json
python manage.py dumpdata app2 --database=db2 > app2_fixture.json
python manage.py dumpdata auth > auth_fixture.json
- 数据导入:
python manage.py loaddata app1_fixture.json --database=db1
python manage.py loaddata app2_fixture.json --database=db2
多数据库联用的缺陷
局限性主要体现在跨数据库关系上。
Django目前不提供跨越对多个数据库
的外键
或多对多关系
的任何支持。如果使用路由器将模型分到到不同的数据库,则由这些模型定义的任何外键和多对多关系必须位于单个数据库
的内部,这是出于完整性的考虑。
如果将Postgres,Oracle或MySQL与InnoDB一起使用,则会在数据库完整性级别强制执行 - 数据库级别的键约束会阻止创建无法验证的关系。但是,可以将SQLite或MySQL与MyISAM表一起使用。
如上面的learning_logs和users2个数据库,在迁移learning_logs就会出现问题,找不到auth_user,因为这个表它在另一个数据库中。
django.db.utils.InternalError: (1824, "Failed to open the referenced table 'auth_user'")
那么在allow_relation
添加 允许两个应用的关联。
elif db_obj1 in ('auth', 'learning_logs') \
and db_obj2 in ('auth', 'learning_logs'):
return True
但迁移时,还是有问题
django.db.utils.InternalError: (1824, "Failed to open the referenced table 'auth_user'")
仍然找不到外键所指的数据表。
关于外键的看法
使用外键的原因无非是要满足某种范式的要求,本质上是一种约束、一种逻辑。数据库上使用外键目的是维护数据强一致性,后果是数据库同步、迁移、分表、分库时各种约束很麻烦。就像Django支持多数据库存储,却不支持跨数据库的外键。
可以将涉及到外键的逻辑全部在应用代码里实现,将逻辑放在代码里本身就是很合理的,双向的话就使用关联表。对于有打算使用多数据库存储的,一开始就考虑不使用外键。确定是从头到尾只在一个数据库中,就无所谓。
数据导入
Django在进行数据库操作时,要有相对应的数据表。
创建数据表的过程:models中创建模型–>使用makemigrations命令生成迁移文件–>migrate修改数据库表结构。
使用迁移文件提供数据
有时候在对数据库进行处理之前,需要填充一些初始数据。
在learning_logs应用下的models.py 下有一个模型ExampleModel
class ExampleModel(models.Model):
name = models.CharField(max_length=10)
content = MDTextField()
对应的数据表:
使用迁移文件的方式为这个模型提供数据。
运行python manage.py makemigrations --empty learning_logs
生成一个空的迁移文件0011_auto_20181104_1624
,内容:
# Generated by Django 2.1.2 on 2018-11-04 08:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('learning_logs', '0010_examplemodel'),
]
operations = [
]
修改这个迁移文件,创建一个新的函数,并让RunPython调用它
# Generated by Django 2.1.2 on 2018-11-04 08:24
from django.db import migrations
def init_name(apps, schema_editor):
# We can't import the ExampleModel model directly as it may be a newer
# version than this migration expects. We use the historical version.
ExampleModel = apps.get_model('learning_logs', 'ExampleModel')
User = apps.get_model('auth', 'User')
for user in User.objects.all():
example = ExampleModel()
example.name = user.username
example.save()
class Migration(migrations.Migration):
dependencies = [
('learning_logs', '0010_examplemodel'),
]
operations = [
migrations.RunPython(init_name),
]
RunPython
需要一个可调用对象作为参数,而这个可调用对象它接受两个参数 :第一个是app注册表,其中包含所有模型的历史版本,用以匹配迁移所在的历史记录;第二个是SchemaEditor,可以用于手动影响数据库架构更改(创建模型、删除模型等)。
在init_name
这个函数中获取了User
和ExampleModel
2个模型,将auth_user表的所有username
数据都导入到learning_logs_examplemodel表的name
中。
运行python mange.py migrate
执行迁移:
获取了全部的username信息。详细内容参见,Django的Writing database migrations
用fixture导入数据
可以使用fixture来导入数据,这些数据不会自动导入,除非使用
TransactionTestCase.fixtures
。所谓fixture就是一个数据的集合,通过它Django就能得知如何将数据导入数据库中。可以使用python manage.py dumpdata
命令来创建一个fixture:
python manage.py dumpdata learning_logs.examplemodel > examplemodel.json
将刚才使用的learning_logs.examplemodel表的数据全部导出到对应的json文件中,不指定具体的模型就会导出所有应用的所有模型。除了使用dumpdata命令,也可以手动编写fixture文件,支持的格式:JSON、XML或YAML。
与dumpdata相对的,可以使用python manage.py loaddata <fixturename>
来将数据添加到数据库,每次执行loaddata时都会重新fixture读取数据,重新加载到数据库。可以在setting
中添加FIXTURE_DIRS
来指定Django搜寻fixture的路径
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixture'), ]
执行从fixture中加载数据。
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ python manage.py loaddata examplemodel
Installed 57 object(s) from 1 fixture(s)
数据库的迁移
Django默认使用SQLite3数据库,当要更换数据库时,需要对整个数据库进行迁移。
python manage.py dumpdata -o=fixture/django_ulysses.json
在另一台上使用migrate命令创建数据库表后,使用loaddata
命令
python manage.py loaddata django_ulysses.json
从json文本中导入数据会出现问题,提示导入content_type
失败
可以看到django_content_type
这张表是与的是用户、用户组相关的
在运行migrate命令时就会有数据添加到这张表内,除非手动删除后再添加。
使用数据库自带导出导入命令
MySQL数据库的mysqldump会导出带有完整sql命令的文件,导入时可以直接导入。
mysqldump [数据库] [表] > [导出的数据库文件]
mysql -u用户名 -p密码 < [要导入的数据库数据]
类似的PostgreSQL的导出导入:
pg_dump [数据库] > [数据文件]
psql -U gpadmin -d your-db -f your-table.sql