前情提要 这篇文章基于前两篇文章Django多数据库历险记(一) 、Django多数据库历险记(二) ,将继续讲述关于Django多数据库的历险记。 好吧其实这篇文章的内容已经和Django多数据库没有太大关系了……只是为了和前两篇文章的命名保持一致才取了这个标题。
秘技:避免物理外键约束
对于数据库层面的物理外键,国内互联网上的声音普遍一致,那就是不推荐使用(比如这个知乎问题 ),公司的DBA也持这个态度。既然如此,就应该想办法在执行Migrate
操作时避免产生物理外键了。 幸运的时,从很早开始(不晚于Django1.8
)Django就提供了直接的手段来避免产生物理外键:db_constraint
属性。从Django DB模块的源码(schema.py )中可以看出,Django在创建Model、添加/修改Field时都会通过db_constraint
属性的值来决定是否加入物理外键约束。实验一下(省略内容见上一篇文章 ):
1 2 3 4 5 6 7 class ChildModel1 (models.Model): name = models.CharField(max_length=255 ) parent1 = models.ForeignKey("Model1" , on_delete=models.CASCADE, db_constraint=False ) parent2 = models.ForeignKey("app_2.Model2" , on_delete=models.CASCADE, db_constraint=False )
1 2 3 4 5 6 7 8 9 10 11 $ python manage.py makemigrations Migrations for 'app_1': app_1/migrations/0002_childmodel1.py - Create model ChildModel1$ $ python manage.py migrate app_1 --database db_1 Operations to perform: Apply all migrations: app_1 Running migrations: Applying app_2.0001_initial... OK Applying app_1.0002_childmodel1... OK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 mariadb root@127 .0 .0 .1 :db_1> select * from django_migrations+ | id | app | name | applied | + | 1 | app_1 | 0001 _initial | 2020 - xx- xx xx:xx:xx.xxxxxx | | 3 | app_2 | 0001 _initial | 2020 - xx- xx xx:xx:xx.xxxxxx | | 3 | app_1 | 0002 _childmodel1 | 2020 - xx- xx xx:xx:xx.xxxxxx | + mariadb root@127 .0 .0 .1 :db_1> show tables+ | Tables_in_db_1 | + | app_1_childmodel1 | | app_1_model1 | | django_migrations | + mariadb root@127 .0 .0 .1 :db_1> show create table app_1_model1CREATE TABLE `app_1_childmodel1` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `name` varchar (255 ) COLLATE utf8mb4_unicode_ci NOT NULL , `parent1_id` int (11 ) NOT NULL , `parent2_id` int (11 ) NOT NULL , PRIMARY KEY (`id`), KEY `app_1_childmodel1_parent1_id_8dfbb0b1` (`parent1_id`), KEY `app_1_childmodel1_parent2_id_db0fdb2e` (`parent2_id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_unicode_ci
秘技:migrations
转SQL语句
在某些特殊场景下(比如处于安全考量),Django自带的migrate
命令可能不被允许运行。如果此时还想使用Django的数据迁移功能,则可以使用sqlmigrate
命令来将创建好的迁移方案转换为SQL语句。比如:
1 2 3 4 5 6 7 $ python manage.py sqlmigrate app_1 0001_initial --database db_1 BEGIN; -- -- Create model Model1 -- CREATE TABLE `app_1_model1` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(255) NOT NULL); COMMIT;
不过这个命令并一次只能转换一条迁移方案,所以我们可以参考魔改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import osimport djangofrom django.conf import settingsfrom django.core.management import execute_from_command_line, CommandParserfrom django.db import DEFAULT_DB_ALIASfrom django.db.migrations.loader import MigrationLoader os.environ.setdefault('DJANGO_SETTINGS_MODULE' , 'multi_db.settings' ) django.setup() parser = CommandParser() parser.add_argument( 'app_label' , nargs='?' , help ='App label of an application to synchronize the state.' , ) app_label = parser.parse_args().app_label loader = MigrationLoader(None ) db = settings.DB_ROUTING.get(app_label, DEFAULT_DB_ALIAS) migrations = sorted (name for app, name in loader.disk_migrations if app == app_label)for migration in migrations: argv = ["manage.py" , "sqlmigrate" , "--database" , db, app_label, migration] print ("\n-- python " + " " .join(argv)) execute_from_command_line(argv)print ("-- my_sqlmigrate complete" )
现在可以一次性转换所有迁移方案了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ python my_sqlmigrate.py app_1 -- python manage.py sqlmigrate --database db_1 app_1 0001_initial BEGIN; -- -- Create model Model1 -- CREATE TABLE `app_1_model1` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(255) NOT NULL); COMMIT; -- python manage.py sqlmigrate --database db_1 app_1 0002_childmodel1 BEGIN; -- -- Create model ChildModel1 -- CREATE TABLE `app_1_childmodel1` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(255) NOT NULL, `parent1_id` integer NOT NULL, `parent2_id` integer NOT NULL); CREATE INDEX `app_1_childmodel1_parent1_id_8dfbb0b1` ON `app_1_childmodel1` (`parent1_id`); CREATE INDEX `app_1_childmodel1_parent2_id_db0fdb2e` ON `app_1_childmodel1` (`parent2_id`); COMMIT; -- my_sqlmigrate complete
总结 文章的实用性可能谈不上有多高,只能说聊以自慰。虽然我在第一篇文章的开头说对多数据库的支持……坑无处不在
,但在深入了解之后,我还是被Django完善的架构给折服了:不愧是最优秀的Python Web框架,我还是too young, too simple。