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

是否应弃用Django ORM改用SQL以降低带宽与查询耗时?

关于Django ORM查询以太坊交易时的性能担忧解答

问题背景

出于兴趣首次使用Django ORM开发以太坊浏览器,定义了以下数据模型:

class AddressModel(models.Model):
    id = models.BigIntegerField(primary_key=True)
    first_seen = models.DateTimeField(db_index=True)
    addr = models.CharField(max_length=42, db_index=True, on_delete=models.PROTECT)
    is_contract = models.BooleanField()
    is_token = models.BooleanField()
    is_wallet = models.BooleanField()

class BlockModel(models.Model):
    id = models.BigIntegerField(primary_key=True)

    number = models.BigIntegerField()
    status = models.CharField(max_length=20)
    timestamp = models.DateTimeField(db_index=True)

    epoch_proposal = models.IntegerField()
    slot_proposal = models.IntegerField()

    fee_recipient = models.ForeignKey(AddressModel, on_delete=models.PROTECT)
    block_reward = models.BigIntegerField()
    total_difficulty = models.CharField(max_length=100)
    size = models.IntegerField()

    gas_used = models.BigIntegerField()
    gas_limit = models.BigIntegerField()
    base_fee_per_gas = models.BigIntegerField()
    burnt_fee = models.BigIntegerField()
    extra_data = models.TextField()

    hash = models.CharField(max_length=66)
    parent_hash = models.CharField(max_length=66)
    state_root = models.CharField(max_length=66)
    withdrawal_root = models.CharField(max_length=66)
    Nonce = models.CharField(max_length=20)

class TransactionModel(models.Model):
    id = models.BigIntegerField(primary_key=True)
    hash = models.CharField(max_length=66)
    block = models.ForeignKey(BlockModel, on_delete=models.PROTECT)
    from_addr = models.ForeignKey('AddressModel', related_name='from_addr', on_delete=models.PROTECT)
    to_addr = models.ForeignKey('AddressModel', related_name='to_addr', on_delete=models.PROTECT)
    input = models.TextField()
    is_valid = models.BooleanField()

担忧点:查询特定from_addr关联的所有交易时,担心ORM会自动获取关联的block完整数据;若该地址有1万条交易,就会返回1万条无需的block数据,造成带宽浪费并增加查询耗时。疑问:这是否属于不应使用ORM的场景?


解答

你的担忧其实是对Django ORM加载机制的误解——默认情况下,Django ORM采用懒加载策略:当你查询addr.from_addr.all()(或TransactionModel.objects.filter(from_addr=addr))时,ORM只会返回TransactionModel的数据,并不会自动加载关联的BlockModel完整数据。只有当你主动访问某个transaction对象的block属性(比如transaction.block.number)时,才会单独查询这条Block数据,此时若有1万条交易,会触发1万次额外查询(N+1问题),才会造成性能损耗。

但解决这个问题非常简单,Django ORM提供了多种方式精准控制查询范围:

  • 只获取交易数据及block_id:如果仅需交易本身的字段和关联的block_id,直接查询时指定需要的字段即可,示例代码:
# 仅查询交易必要字段和block_id,不会加载BlockModel实例
target_addr = AddressModel.objects.get(addr="0x...")
transactions = TransactionModel.objects.filter(from_addr=target_addr).only(
    'id', 'hash', 'from_addr_id', 'to_addr_id', 'input', 'is_valid', 'block_id'
)

此时返回的每个transaction对象中,block_id就是关联的Block主键,和原生SQL返回结果完全一致,不会额外查询Block数据。

  • 按需加载Block的部分字段:如果确实需要Block的部分信息(比如区块号、时间戳),可以用select_related结合only/defer做连表查询,只获取需要的Block字段,避免加载整个Block对象:
# 连表查询,但仅获取交易字段和Block的number、timestamp
transactions = TransactionModel.objects.filter(from_addr=target_addr).select_related('block').only(
    'id', 'hash', 'input', 'block__number', 'block__timestamp'
)

这种方式只会执行一次SQL联表查询,返回结果包含需要的Block字段,不会加载不必要的数据(如extra_data、state_root等)。

  • 避免N+1查询:若之前不小心触发了N+1问题,针对外键关联(Transaction到Block是多对一),使用select_related可以一次性加载所有关联的Block数据,替代多次单独查询。

总结:这绝对不属于不该用ORM的场景。Django ORM的灵活性完全能满足这类性能需求,只要掌握好字段选择和关联加载的控制方法,既能享受ORM带来的开发效率,又能达到和原生SQL相近的性能表现。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 04:47:36