You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django导入iTunes数据后Admin页面加载异常求助

Django Admin加载大量关联数据无响应的排查求助

我已将外部iTunes数据导入Django自有数据库,包含783122首歌曲、59445张专辑、97562位艺术家。配置Django Admin后,期望在艺术家或专辑页面内联显示关联歌曲,但访问页面时无报错,持续加载超过30分钟仍无响应。已将自然键替换为bigint主键,问题依旧。怀疑索引创建耗时,但数据量未超百万行,耗时过长不符合预期,求排查思路,是否需要换用Flask?

模型代码

from django.db import models


class ArtistKeys(models.Model):
    naturalkey = models.CharField(unique=True, max_length=90)

class AlbumKeys(models.Model):
    naturalkey = models.CharField(unique=True, max_length=90)

class SongKeys(models.Model):
    persistentid = models.CharField(unique=True, max_length=90)

class ItunesArtist(models.Model):
    def __str__(self):
        if self.name:
            return self.name
        else:
            return ""
    id = models.IntegerField(primary_key=True)
    naturalkey = models.CharField(unique=True, max_length=90)
    name = models.CharField(max_length=600, blank=True, null=True)
    expressed = models.BooleanField()
    
# class ItunesAlbumArtist(models.Model):
#     albumartist = models.CharField(primary_key=True, max_length=90)
#     name = models.CharField(max_length=600, blank=True, null=True)
    
class ItunesAlbum(models.Model):
    def __str__(self):
        if self.name:
            return self.name
        else:
            return ""
    id = models.BigIntegerField(primary_key=True)
    naturalkey = models.CharField(unique=True, max_length=90)
    name = models.CharField(max_length=600, blank=True, null=True)
    samplerate = models.IntegerField(blank=True, null=True)  
    trackcount = models.SmallIntegerField(blank=True, null=True)

class ItunesSong(models.Model):
    def __str__(self):
#        return "{} – {} – {}".format(self.artist, self.album, self.name)
        if self.name:
            return self.name
        else:
            return ""
    id = models.BigIntegerField(primary_key=True)
    album = models.CharField(max_length=600, blank=True, null=True)  
    artist = models.CharField(max_length=600, blank=True, null=True)  
    name = models.CharField(max_length=600, blank=True, null=True)  
    albumartist = models.CharField(max_length=600, blank=True, null=True)  
    albumrating = models.SmallIntegerField(blank=True, null=True)  
    albumratingcomputed = models.BooleanField(blank=True, null=True)  
    artworkcount = models.SmallIntegerField(blank=True, null=True)  
    bpm = models.SmallIntegerField(blank=True, null=True)  
    bitrate = models.SmallIntegerField(blank=True, null=True)  
    clean = models.BooleanField(blank=True, null=True)  
    comments = models.CharField(max_length=360, blank=True, null=True)  
    composer = models.CharField(max_length=360, blank=True, null=True)  
    contentrating = models.CharField(max_length=60, blank=True, null=True)  
    dateadded = models.DateTimeField(blank=True, null=True)  
    datemodified = models.DateTimeField(blank=True, null=True)  
    disabled = models.BooleanField(blank=True, null=True)  
    disccount = models.SmallIntegerField(blank=True, null=True)  
    discnumber = models.SmallIntegerField(blank=True, null=True)  
    disliked = models.BooleanField(blank=True, null=True)  
    episode = models.CharField(max_length=20, blank=True, null=True)  
    episodeorder = models.IntegerField(blank=True, null=True)  
    explicit = models.BooleanField(blank=True, null=True)  
    filetype = models.BigIntegerField(blank=True, null=True)  
    genre = models.CharField(max_length=100, blank=True, null=True)  
    grouping = models.CharField(max_length=180, blank=True, null=True)  
    hd = models.BooleanField(blank=True, null=True)  
    hasvideo = models.BooleanField(blank=True, null=True)  
    kind = models.CharField(max_length=40, blank=True, null=True)  
    location = models.CharField(max_length=1070, blank=True, null=True)  
    loved = models.BooleanField(blank=True, null=True)  
    movie = models.BooleanField(blank=True, null=True)  
    normalization = models.IntegerField(blank=True, null=True)  
    partofgaplessalbum = models.BooleanField(blank=True, null=True)  
    persistentid = models.CharField(unique=True, max_length=20)  
    playcount = models.SmallIntegerField(blank=True, null=True)  
    playdate = models.BigIntegerField(blank=True, null=True)  
    playdateutc = models.DateTimeField(blank=True, null=True)  
    podcast = models.BooleanField(blank=True, null=True)  
    protected = models.BooleanField(blank=True, null=True)  
    purchased = models.BooleanField(blank=True, null=True)  
    rating = models.SmallIntegerField(blank=True, null=True)  
    ratingcomputed = models.BooleanField(blank=True, null=True)  
    releasedate = models.DateTimeField(blank=True, null=True)  
    samplerate = models.IntegerField(blank=True, null=True)  
    season = models.SmallIntegerField(blank=True, null=True)  
    series = models.CharField(max_length=120, blank=True, null=True)  
    size = models.BigIntegerField(blank=True, null=True)  
    skipcount = models.SmallIntegerField(blank=True, null=True)  
    skipdate = models.DateTimeField(blank=True, null=True)  
    sortalbum = models.CharField(max_length=600, blank=True, null=True)  
    sortalbumartist = models.CharField(max_length=600, blank=True, null=True)  
    sortartist = models.CharField(max_length=600, blank=True, null=True)  
    sortcomposer = models.CharField(max_length=600, blank=True, null=True)  
    sortname = models.CharField(max_length=600, blank=True, null=True)  
    sortseries = models.CharField(max_length=600, blank=True, null=True)  
    tvshow = models.BooleanField(blank=True, null=True)  
    totaltime = models.IntegerField(blank=True, null=True)  
    trackcount = models.SmallIntegerField(blank=True, null=True)  
    tracknumber = models.IntegerField(blank=True, null=True)  
    tracktype = models.CharField(max_length=10, blank=True, null=True)  
    unplayed = models.BooleanField(blank=True, null=True)  
    videoheight = models.SmallIntegerField(blank=True, null=True)  
    videowidth = models.SmallIntegerField(blank=True, null=True)  
    volumeadjustment = models.SmallIntegerField(blank=True, null=True)  
    work = models.CharField(max_length=80, blank=True, null=True)  
    year = models.SmallIntegerField(blank=True, null=True)  
    created = models.DateTimeField(blank=True, null=True)  
    modified = models.DateTimeField(blank=True, null=True)  
    releaseid = models.ForeignKey(
        ItunesAlbum, 
        max_length=90, 
        blank=True, 
        null=True, 
        on_delete=models.PROTECT,
    )  
    artistid = models.ForeignKey(
        ItunesArtist, 
        max_length=90, 
        blank=True, 
        null=True, 
        on_delete=models.PROTECT,
        related_name="artist_songs",
        related_query_name="artist_songs",
    )  
    expressedartistid = models.ForeignKey(
        ItunesArtist, 
        max_length=90, 
        blank=True, 
        null=True, 
        on_delete=models.PROTECT,
        related_name="expressed_artist_songs",
        related_query_name="expressed_artist_songs",
    )  
    albumartistid = models.ForeignKey(
        ItunesArtist, 
        max_length=90, 
        blank=True, 
        null=True, 
        on_delete=models.PROTECT,
        related_name="album_artist_songs",
        related_query_name="album_artist_songs",
    )
    compilation = models.BooleanField(blank=True, null=True)  

Admin代码

from django.contrib import admin
from .models import ItunesArtist, ItunesAlbum, ItunesSong

class ArtistSongInline(admin.TabularInline):
    fk_name = 'artistid'
    model = ItunesSong
    
class ExpressedArtistSongInline(admin.TabularInline):
    fk_name = 'expressedartistid'
    model = ItunesSong
    
class AlbumArtistSongInline(admin.TabularInline):
    model = ItunesSong
    
class ItunesArtistAdmin(admin.ModelAdmin):
    inlines = [ExpressedArtistSongInline]

class ItunesAlbumAdmin(admin.ModelAdmin):
    inlines = [AlbumArtistSongInline]


admin.site.register(ItunesArtist, ItunesArtistAdmin)
admin.site.register(ItunesAlbum, ItunesAlbumAdmin)
admin.site.register(ItunesSong)

排查与优化思路

1. 确认数据库索引是否生效

Django外键默认会创建索引,但手动导入数据后可能存在索引未创建的情况。直接查询数据库验证:

  • PostgreSQL:\d itunes_song 查看索引列表
  • MySQL:SHOW INDEX FROM itunes_song;

如果外键字段(expressedartistid、albumartistid、releaseid等)无索引,手动创建:

CREATE INDEX idx_itunes_song_expressedartistid ON itunes_song(expressedartistid);
CREATE INDEX idx_itunes_song_albumartistid ON itunes_song(albumartistid);
CREATE INDEX idx_itunes_song_releaseid ON itunes_song(releaseid);

2. 限制内联数据量与字段

Django Admin默认会加载所有关联数据,热门艺术家可能关联数千首歌,全量加载必然导致超时。修改Inline配置:

class ExpressedArtistSongInline(admin.TabularInline):
    fk_name = 'expressedartistid'
    model = ItunesSong
    fields = ('name', 'album', 'year')  # 只显示必要字段,减少数据传输
    extra = 0  # 不显示空表单
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        # 限制显示数量+预关联专辑表,避免N+1查询
        return qs.select_related('releaseid')[:100]

3. 查看慢查询日志定位问题

开启Django数据库查询日志,查看页面加载时的SQL执行情况:
在settings.py中添加日志配置:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {'class': 'logging.StreamHandler'},
    },
    'loggers': {
        'django.db.backends': {
            'level': 'DEBUG',
            'handlers': ['console'],
        },
    },
}

运行服务器后访问页面,观察控制台输出的SQL,重点检查是否存在全表扫描、未使用索引的查询。

4. 替换内联为跳转链接

对于关联数据量大的场景,内联显示本身不合理,改为在Admin列表页添加跳转链接,直接筛选该艺术家/专辑的所有歌曲:

class ItunesArtistAdmin(admin.ModelAdmin):
    def songs_link(self, obj):
        from django.urls import reverse
        # 生成带筛选条件的歌曲列表链接
        url = reverse('admin:your_app_name_itunessong_changelist') + f'?expressedartistid__id={obj.id}'
        return f'<a href="{url}">查看所有关联歌曲</a>'
    songs_link.allow_tags = True
    songs_link.short_description = '关联歌曲'
    list_display = ('name', 'expressed', 'songs_link')

class ItunesAlbumAdmin(admin.ModelAdmin):
    def songs_link(self, obj):
        from django.urls import reverse
        url = reverse('admin:your_app_name_itunessong_changelist') + f'?releaseid__id={obj.id}'
        return f'<a href="{url}">查看所有关联歌曲</a>'
    songs_link.allow_tags = True
    songs_link.short_description = '关联歌曲'
    list_display = ('name', 'trackcount', 'songs_link')

5. 优化数据库配置

检查数据库缓存配置,确保有足够内存缓存数据:

  • PostgreSQL:调整shared_buffers(建议设为系统内存的1/4)
  • MySQL:调整innodb_buffer_pool_size(建议设为系统内存的50%-70%)

关于是否换用Flask

完全没必要,Django可以轻松处理百万级数据量,当前问题是Admin配置未优化导致的,而非框架本身的承载能力问题。

内容的提问来源于stack exchange,提问作者Jason Witherspoon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:52:32