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

PostgreSQL 9.6:单事务内批量插入并同步更新用户产品计数

Hey there, let's work through your problem. You need to batch insert 20k product records in a transaction while updating the user's products_count in the users table, plus keep that count in sync when products get deleted. Here's how to handle this reliably, with multiple options depending on your preference:

1. Batch Insert + Sync Count in a Single Transaction

First, wrapping everything in a transaction is non-negotiable—it ensures if any step fails, all changes roll back, so you never end up with mismatched product records and counts.

Option A: Raw SQL (with safety notes)

If you're using raw SQL like your example, make sure to avoid SQL injection risks (never directly interpolate user input!). Here's how to structure it:

user_id = 123 # Target user ID
product_ids = (1..20000).to_a # Your list of product IDs

# Build values for bulk insert
values = product_ids.map do |pid|
  # Use UTC time formatted correctly for your DB
  "('#{pid}', '#{user_id}', '#{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')}')"
end.join(', ')

ActiveRecord::Base.transaction do
  # Execute bulk insert
  insert_sql = "INSERT INTO products(product_id, user_id, created_at) VALUES #{values}"
  ActiveRecord::Base.connection.execute(insert_sql)

  # Get number of rows actually inserted (DB-specific syntax)
  # MySQL uses ROW_COUNT(); PostgreSQL uses GETDIAGNOSTICS row_count = ROW_COUNT;
  inserted_count = ActiveRecord::Base.connection.execute("SELECT ROW_COUNT() AS count").first['count']

  # Update the user's product count
  ActiveRecord::Base.connection.execute(
    "UPDATE users SET products_count = products_count + #{inserted_count} WHERE id = #{user_id}"
  )
end

⚠️ Critical Note: If product_id or user_id comes from user input, skip direct string interpolation—use parameterized queries to avoid SQL injection.

Option B: ActiveRecord (Safer, Rails-Friendly)

If you're using Rails, insert_all handles parameterization automatically and returns the number of successfully created records (great if you have unique constraints that might skip duplicates):

user_id = 123
product_ids = (1..20000).to_a

ActiveRecord::Base.transaction do
  # Bulk insert products, get the count of actually created records
  creation_result = Product.insert_all(
    product_ids.map { |pid| { product_id: pid, user_id: user_id, created_at: Time.now.utc } }
  )

  # Update the user's count
  User.where(id: user_id).update_all("products_count = products_count + #{creation_result.count}")
end
2. Sync Count When Deleting Products

Just like inserts, deletions need to update the count in a transaction to avoid inconsistencies.

Single Product Deletion

product = Product.find_by(id: target_product_id, user_id: user_id)

ActiveRecord::Base.transaction do
  product.destroy
  User.where(id: user_id).update_all("products_count = products_count - 1")
end

Bulk Product Deletion

product_ids_to_delete = [101, 102, 103, ...] # Your list of IDs to delete

ActiveRecord::Base.transaction do
  deleted_count = Product.where(id: product_ids_to_delete, user_id: user_id).delete_all
  User.where(id: user_id).update_all("products_count = products_count - #{deleted_count}")
end
3. The Most Reliable Option: Database Triggers

Manually updating counts in code works, but it's easy to miss edge cases (like someone running direct SQL to delete products). Database triggers automate this sync, so counts stay correct no matter how the data is modified.

MySQL Triggers

After Insert Trigger

DELIMITER //
CREATE TRIGGER update_product_count_after_insert
AFTER INSERT ON products
FOR EACH ROW
BEGIN
  UPDATE users SET products_count = products_count + 1 WHERE id = NEW.user_id;
END //
DELIMITER ;

After Delete Trigger

DELIMITER //
CREATE TRIGGER update_product_count_after_delete
AFTER DELETE ON products
FOR EACH ROW
BEGIN
  UPDATE users SET products_count = products_count - 1 WHERE id = OLD.user_id;
END //
DELIMITER ;

PostgreSQL Triggers

First create a helper function:

CREATE OR REPLACE FUNCTION update_user_product_count()
RETURNS TRIGGER AS $$
BEGIN
  IF TG_OP = 'INSERT' THEN
    UPDATE users SET products_count = products_count + 1 WHERE id = NEW.user_id;
  ELSIF TG_OP = 'DELETE' THEN
    UPDATE users SET products_count = products_count - 1 WHERE id = OLD.user_id;
  END IF;
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

Then attach triggers to the products table:

CREATE TRIGGER trigger_product_insert
AFTER INSERT ON products
FOR EACH ROW EXECUTE FUNCTION update_user_product_count();

CREATE TRIGGER trigger_product_delete
AFTER DELETE ON products
FOR EACH ROW EXECUTE FUNCTION update_user_product_count();

Triggers take the guesswork out of maintaining counts—they run automatically every time a product is added or removed, so you don't have to remember to update the count in every code path.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:04:19