如何为数据库查询实现分页?

How to implement pagination for a database query?

我是长生不老药编程的新手。我在 elixir 中遇到递归问题。

def seen_page(page_id, total \ 0, type \ "ALL") do
    repo = Shard.by_page(page_id)
    sub_query =
      if type === "ALL" do
        from(c in Conversation, where: c.page_id == ^page_id and c.seen == false, limit: 500)
      else
        from(c in Conversation, where: c.page_id == ^page_id and c.seen == false and c.type == ^type, limit: 500)
      end

    from(c in Conversation, where: c.page_id == ^page_id, join: s in subquery(sub_query), on: s.id == c.id)
    |> repo.update_all([set: [seen: true, unread_count: 0]])
    |> case  do
      {count, nil} ->
        case count do
          500 ->
            seen_page(page_id, total + 500, type)
          count ->
            IO.puts "Đã đánh dấu tất cả #{type} của trang #{page_id} thành đã đọc"
            Cache.update_page_unread_count_limit_2k(page_id)
            total + count
        end
      _ ->
         false
    end
  end

首先,了解网络请求应该快速收到响应。响应时间过长的网站可能会给访问者带来糟糕的体验——即使是适度缓慢的响应也会导致访问者彻底离开。我手边没有 link,但我记得看到一篇文章表明,如果事情没有发生,访问者将在大约 3 秒内离开页面。如果响应花费的时间太长,也有可能完全超时。简而言之,反应慢是不好的——它们甚至在某些 web attacks.

中发挥了作用

所以你应该永远不要将你的长运行进程直接连接到网页中。如果您需要触发花费超过几百毫秒的时间,您应该始终异步 处理它,这样访问者就不会等待。您只需快速告诉您的访问者“好的,我们正在努力”并完成。

在 Elixir 中执行此操作的一种简单方法是通过 Task.start/3。例如,如果你的慢任务是 Foo 模块中的 :slow_thing 函数,你可以像这样异步调用它:

result = Task.start(Foo, :slow_thing, ["Hello..."])
IO.inspect(result)

请注意,发送邮件会立即result 类似于 {:ok, #PID<0.99.0>} —— 它对 Foo.slow_thing/1 函数的作用或可能需要多长时间的了解为零。它知道模块和函数存在,但仅此而已。

例如,如果您异步调用的模块和函数如下所示:

defmodule Foo do
  def slow_thing(input) do
    Process.sleep(5_000) # <-- simulated long-running thing
    File.write("side_effect.log","#{input}\n", [:utf8, :append])
  end
end

然后您可以检查预期的副作用(在本例中查看日志文件)。请记住,调用 Task.start/3 的过程必须仍然是 运行 才能使 Foo.slow_thing/1 完成。如果您在 Phoenix 应用程序中执行此操作,这应该不是问题,但如果您试图在一个简单的 .exs 脚本中执行此操作,请记住,一旦脚本完成,启动的任务将关闭。

既然你问的是如何跟踪进度,事情就有点复杂了。认真地问问自己,这是一项要求还是只是一件幸事 -- 它可能不值得付出额外的工作

解决这类问题的诀窍是递归:一个调用自身的函数。它仍然应该被异步调用,但在每次迭代期间它可以调用一个您可以观察到的副作用。考虑这样的事情,它显示了用于对 select 查询进行分页的递归:

def update_conversations(page_id, offset \ 0) do
  from(c in Conversation, where: c.page_id == ^page_id and c.seen == false, limit: 500, offset: ^offset)
  |> Repo.all()
  |> case do
    [] ->
      # another side-effect somewhere to indicate completion
      SomeSideEffect.done(page_id)
      :done
    results ->
      # side-effect somewhere to indicate progress
      SomeSideEffect.progress(page_id, 500)
      update_conversations(page_id, offset + 500)  # <-- recursion!
  end
end

您可以使更新查询适应这种类型的模式,但重要的是您要选择有用的副作用。网页可以读取“进度缓存”的值,每次访问者刷新页面时,他们都可以看到进度。如果您不想强制刷新,您可以 Javascript 定期查询端点或使用 LiveView 将更新推送给访问者。