【问题标题】:Is it possible to cascade active record deletion in batches?是否可以批量级联删除活动记录?
【发布时间】:2020-02-03 09:14:51
【问题描述】:

我有很多数据要删除,因此我使用 delete/delete_all 而不是 destroy,通过外键上的级联 (on_delete: :cascade)。

我想删除一个父活动记录,该记录有几个“子表”和很多行。其中一些子表也有几个子表。出于这个原因,我在外键上添加了级联,这样我只需要调用 parent.delete 即可触发删除父级的所有子级和孙级。

我想将 delete/delete_all 与活动记录批次组合https://api.rubyonrails.org/classes/ActiveRecord/Batches.html,但由于只有一个单亲,我不知道如何以一种简洁的方式组合批次和级联删除。

一种选择是显式批量删除孩子和孙子,例如

parent.children_x.find_each do |child_x|
  child_x.grand_children_y.in_batches do |grand_child_y_batch|
    grand_child_y_batch.delete_all        
  end
  child_x.delete        
end
parent.children_z.in_batches do |child_batch|
  child_batch.delete_all
end
...etc...

但如果有一种更隐含的方式允许我只在父级上调用 delete 并批量删除子级和孙子级,那将是可取的,例如

parent.cascade_in_batches do |parent_batch|
  parent_batch.delete_all #This batch deletes all children and grand children
end

我看到父级上没有in_batches,因为父级只是一个实体,所以看起来只有像上面第一个示例那样分批显式删除才有可能?

谢谢,

-露易丝

【问题讨论】:

    标签: ruby-on-rails rails-activerecord cascade


    【解决方案1】:

    你真的只需要设置外键来级联,Postgres 会一直负责删除。由于这是在数据库层实现的,因此如何从 Rails 触发删除并不重要。

    class CreateCountries < ActiveRecord::Migration[6.0]
      def change
        create_table :countries do |t|
          t.string :name
          t.timestamps
        end
      end
    end
    
    class CreateStates < ActiveRecord::Migration[6.0]
      def change
        create_table :states do |t|
          t.string :name
          t.belongs_to :country, null: false, foreign_key: {on_delete: :cascade}
          t.timestamps
        end
      end
    end
    
    class CreateCities < ActiveRecord::Migration[6.0]
      def change
        create_table :cities do |t|
          t.string :name
          t.belongs_to :state, null: false, foreign_key: {on_delete: :cascade}
          t.timestamps
        end
      end
    end
    

    型号:

    class Country < ApplicationRecord
      has_many :states
      has_many :cities, through: :states
    end
    
    class State < ApplicationRecord
      belongs_to :country
      has_many :cities
    end
    
    class City < ApplicationRecord
      belongs_to :state
      has_one :country, through: :state
    end
    

    通过规范:

    require 'rails_helper'
    
    RSpec.describe Country, type: :model do
      describe "cascading delete" do
        let!(:country){ Country.create }
        let!(:state){ country.states.create }
        let!(:city){ state.cities.create }
    
        it "deletes the states" do
          expect {
            country.delete
          }.to change(State, :count).from(1).to(0)
        end
    
        it "deletes the cities" do
          expect {
            Country.delete_all
          }.to change(City, :count).from(1).to(0)
        end
      end
    end
    

    如果您在此处使用.each_with_batches 与否在这里无关紧要。任何创建DELETE FROM countries 查询的东西都会触发该数据库触发器。除非你真的需要评估是否应该在 Rails 中删除每个父级,否则你应该能够做到:

    Country.where(evil: true).delete_all
    

    这将比.find_each 更有效率,因为您只是在执行一个 SQL 查询。如果您遍历记录,则每行执行一个 DELETE FROM coutries WHERE id = ? 查询,并且由于其阻塞 Rails 必须等待到数据库的往返。

    【讨论】:

    • 啊,好吧-只是为了确保我理解:由 parent.delete 触发的级联删除将为每个表、子表和祖父表生成一个 SQL 删除语句 á la "DELETE FROM X where in (...)",这意味着在这种情况下不需要批处理功能?在你写的时候,如果你想使用destroy或者如果你想使用对象做一些逻辑来确定是否应该删除对象,那么只需要批处理功能?
    • 不——你还是不明白。在规范中的示例中,当您调用country.delete 时,查询只是DELETE FROM countries WHERE countries.id = ?;。如果您调用 Country.delete_all 它是相同的查询,但没有 WHERE 子句。级联纯粹是在数据库端完成的——Rails 不知道它的存在。当您创建删除查询时,Postgres 知道有一个指向国家表的外键约束。
    • 当外键设置为ON DELETE CASCADE postgres 将自动删除州上的行,因为城市也有外键约束(州上)这些行也被删除。可以把它想象成一个在删除行之前运行的数据库触发器,以便保持引用完整性。
    • 啊,好的,非常感谢。我看到活动记录批次示例包括带有 in_batches 和 delete_all 的示例:Person.where("age &gt; 21").in_batches do |relation| relation.delete_all sleep(10) # Throttle the delete queries end 该示例不会比直接执行 Person.where("age > 21").delete_all 效率低,因为没有 in_batches 的代码仅使用一个SQL查询?如果是这样,该示例的意义何在 - 在耗时的删除期间提供非阻塞时段?
    • 是的,它不太有效。我想可能会有一些罕见的情况,你有数百万行并且想要这样做,但我认为这主要是一个人为的例子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-15
    • 2020-08-17
    • 2015-05-20
    • 1970-01-01
    • 2015-03-04
    • 2016-12-16
    • 1970-01-01
    相关资源
    最近更新 更多