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

Tds适配器仅支持on_conflict: :raise的冲突更新替代方案咨询

问题根因

Tds(Ecto适配SQL Server的适配器)当前版本确实仅支持on_conflict: :raise配置,没有实现PostgreSQL风格的on_conflict增量更新等高级能力,这是适配器本身的功能限制,和业务逻辑代码写法无关。

可落地的替代实现方案

两种方案都可以实现和教程完全一致的「商品重复加入购物车时数量自动累加」效果,可根据性能需求选择:

  • 方案1:事务加行锁的先查后写(推荐,代码符合Ecto常规写法,维护成本低)
    利用数据库事务+行级锁规避并发竞态问题,逻辑和原生UPSERT效果完全一致,代码示例:

    def add_item_to_cart(%Cart{} = cart, %Catalog.Product{} = product) do
      Repo.transaction(fn ->
        # 查询对应购物车商品条目时加更新锁,避免并发操作导致的数据错误
        case Repo.one(from ci in CartItem,
               where: ci.cart_id == ^cart.id and ci.product_id == ^product.id,
               lock: "UPDLOCK"
             ) do
          nil ->
            # 条目不存在则新建初始数量为1的记录
            %CartItem{quantity: 1, price_when_carted: product.price}
            |> CartItem.changeset(%{})
            |> Ecto.Changeset.put_assoc(:cart, cart)
            |> Ecto.Changeset.put_assoc(:product, product)
            |> Repo.insert!()
    
          existing_item ->
            # 条目已存在则执行数量+1更新
            existing_item
            |> Ecto.Changeset.change(quantity: existing_item.quantity + 1)
            |> Repo.update!()
        end
      end)
    end
    

    说明:SQL Server的UPDLOCK是更新锁,会锁定查询到的行直到事务结束,能彻底避免高并发下重复插入、计数不准的问题,不需要依赖适配器的on_conflict能力。

  • 方案2:原生SQL MERGE语句实现UPSERT(追求极致性能可选)
    SQL Server原生支持MERGE语法完成单语句UPSERT,可以直接通过Ecto的原生SQL接口执行,性能和数据库原生UPSERT一致,代码示例:

    def add_item_to_cart(%Cart{} = cart, %Catalog.Product{} = product) do
      merge_sql = """
      MERGE cart_items AS target
      USING (VALUES (@cart_id, @product_id, @init_price, @init_quantity))
        AS source (cart_id, product_id, price_when_carted, quantity)
      ON target.cart_id = source.cart_id AND target.product_id = source.product_id
      WHEN MATCHED THEN
        UPDATE SET quantity = target.quantity + 1
      WHEN NOT MATCHED THEN
        INSERT (cart_id, product_id, price_when_carted, quantity)
        VALUES (source.cart_id, source.product_id, source.price_when_carted, source.quantity);
      """
    
      Ecto.Adapters.SQL.query!(Repo, merge_sql,
        cart_id: cart.id,
        product_id: product.id,
        init_price: product.price,
        init_quantity: 1
      )
    end
    

    说明:使用时需要把SQL里的表名、字段名和实际的数据库表结构对齐,参数通过绑定传入,不存在SQL注入风险。

注意:不要使用无锁的裸先查后写逻辑,高并发场景下会出现唯一键冲突、重复购物车条目、数量计数错误等问题。

内容的提问来源于stack exchange,提问作者Erick Concepción

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 10:54:16