【问题标题】:why the diesel new pagination sql did not work为什么柴油新分页 sql 不起作用
【发布时间】:2022-12-10 10:08:57
【问题描述】:

我正在使用 diesel diesel = { version = "1.4.8", features = ["postgres","64-column-tables","chrono"] } 对 rust 1.59.0 进行分页,这是在 diesel 中进行分页查询的关键部分:

use diesel::pg::Pg;
use diesel::query_builder::{AstPass, QueryFragment};
use diesel::QueryResult;
use diesel::sql_types::BigInt;
use crate::common::query::pagination::Paginated;

pub fn handle_table_query<T: QueryFragment<Pg>>(this: &Paginated<T>, mut out: AstPass<Pg>) -> QueryResult<()> {
    out.push_sql("SELECT *, COUNT(*) OVER () FROM ");
    if this.is_sub_query {
        out.push_sql("(");
    }
    this.query.walk_ast(out.reborrow())?;
    if this.is_sub_query {
        out.push_sql(")");
    }
    out.push_sql(" t LIMIT ");
    out.push_bind_param::<BigInt, _>(&this.per_page)?;
    out.push_sql(" OFFSET ");
    let offset = (this.page - 1) * this.per_page;
    out.push_bind_param::<BigInt, _>(&offset)?;
    Ok(())
}

此代码将生成这样的 sql:

select
    *,
    COUNT(*) over ()
from
    (
    select
        "article"."id",
        "article"."user_id",
        "article"."title",
        "article"."author",
        "article"."guid",
        "article"."created_time",
        "article"."updated_time",
        "article"."link",
        "article"."pub_time",
        "article"."sub_source_id",
        "article"."cover_image",
        "article"."channel_reputation",
        "article"."editor_pick"
    from
        "article"
    where
        "article"."id" > $1) t
limit $2 offset $3

如您所知,此 sql 有一个大问题。当文章表数据增加时。此子查询将导致序列扫描。现在article表有2000000行,每次查询都在20s以上。我想做的是删除窗口函数并将限制条件移动到子查询中,最终 sql 将如下所示:

select
 *,
 count_estimate('select * from article')
from
 (
 select
  "article"."id",
  "article"."user_id",
  "article"."title",
  "article"."author",
  "article"."guid",
  "article"."created_time",
  "article"."updated_time",
  "article"."link",
  "article"."pub_time",
  "article"."sub_source_id",
  "article"."cover_image",
  "article"."channel_reputation",
  "article"."editor_pick"
 from
  "article"
 where
  "article"."id" > $1 limit $2 offset $3 ) t

这个 sql 只需要不到 100ms。这是我正在调整的 Rust 代码:

pub fn handle_big_table_query<T: QueryFragment<Pg>>(this: &Paginated<T>, mut out: AstPass<Pg>)-> QueryResult<()>{
    out.push_sql("SELECT *, count_estimate('select * from article') FROM ");
    if this.is_sub_query {
        out.push_sql("(");
    }
    this.query.walk_ast(out.reborrow())?;
    if this.is_sub_query {
        out.push_sql(" t LIMIT ");
        out.push_bind_param::<BigInt, _>(&this.per_page)?;
        out.push_sql(" OFFSET ");
        let offset = (this.page - 1) * this.per_page;
        out.push_bind_param::<BigInt, _>(&offset)?;
        out.push_sql(")");
    }
    Ok(())
}

令我惊讶的是,这个新代码生成的 sql 没有返回任何内容。可以看到sql吗?我检查了我的 Rust 源代码,但没有弄清楚哪里出了问题。这是完整的分页代码:

use diesel::prelude::*;
use diesel::query_dsl::methods::LoadQuery;
use diesel::query_builder::{QueryFragment, Query, AstPass};
use diesel::pg::Pg;
use diesel::sql_types::BigInt;
use diesel::QueryId;
use serde::{Serialize, Deserialize};
use crate::common::query::page_query_handler::{handle_big_table_query, handle_table_query};

pub trait PaginateForQueryFragment: Sized {
    fn paginate(self, page: i64, is_big_table: bool) -> Paginated<Self>;
}

impl<T> PaginateForQueryFragment for T
    where T: QueryFragment<Pg>{
    fn paginate(self, page: i64, is_big_table: bool) -> Paginated<Self> {
        Paginated {
            query: self,
            per_page: 10,
            page,
            is_sub_query: true,
            is_big_table
        }
    }
}

#[derive(Debug, Clone, Copy, QueryId, Serialize, Deserialize, Default)]
pub struct Paginated<T> {
    pub query: T,
    pub page: i64,
    pub per_page: i64,
    pub is_sub_query: bool,
    pub is_big_table: bool
}

impl<T> Paginated<T> {
    pub fn per_page(self, per_page: i64) -> Self {
        Paginated { per_page, ..self }
    }

    pub fn load_and_count_pages<U>(self, conn: &PgConnection) -> QueryResult<(Vec<U>, i64)>
        where
            Self: LoadQuery<PgConnection, (U, i64)>,
    {
        let per_page = self.per_page;
        let results = self.load::<(U, i64)>(conn)?;
        let total = results.get(0).map(|x| x.1).unwrap_or(0);
        let records = results.into_iter().map(|x| x.0).collect();
        let total_pages = (total as f64 / per_page as f64).ceil() as i64;
        Ok((records, total_pages))
    }

    pub fn load_and_count_pages_total<U>(self, conn: &PgConnection) -> QueryResult<(Vec<U>, i64, i64)>
        where
            Self: LoadQuery<PgConnection, (U, i64)>,
    {
        let per_page = self.per_page;
        let results = self.load::<(U, i64)>(conn)?;
        let total = results.get(0).map(|x| x.1).unwrap_or(0);
        let records = results.into_iter().map(|x| x.0).collect();
        let total_pages = (total as f64 / per_page as f64).ceil() as i64;
        Ok((records, total_pages,total))
    }
}

impl<T: Query> Query for Paginated<T> {
    type SqlType = (T::SqlType, BigInt);
}

impl<T> RunQueryDsl<PgConnection> for Paginated<T> {}


impl<T> QueryFragment<Pg> for Paginated<T>
    where
        T: QueryFragment<Pg>,
{
    fn walk_ast(&self, mut out: AstPass<Pg>) -> QueryResult<()> {
        if self.is_big_table {
            handle_big_table_query(&self, out);
        }else{
            handle_table_query(&self,out);
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Copy, QueryId)]
pub struct QuerySourceToQueryFragment<T> {
    query_source: T,
}

impl<FC, T> QueryFragment<Pg> for QuerySourceToQueryFragment<T>
    where
        FC: QueryFragment<Pg>,
        T: QuerySource<FromClause=FC>,
{
    fn walk_ast(&self, mut out: AstPass<Pg>) -> QueryResult<()> {
        self.query_source.from_clause().walk_ast(out.reborrow())?;
        Ok(())
    }
}

pub trait PaginateForQuerySource: Sized {
    fn paginate(self, page: i64, is_big_table: bool) -> Paginated<QuerySourceToQueryFragment<Self>>;
}

impl<T> PaginateForQuerySource for T
    where T: QuerySource {
    fn paginate(self, page: i64, is_big_table: bool) -> Paginated<QuerySourceToQueryFragment<Self>> {
        Paginated {
            query: QuerySourceToQueryFragment {query_source: self},
            per_page: 10,
            page,
            is_sub_query: false,
            is_big_table
        }
    }
}

【问题讨论】:

    标签: postgresql rust rust-diesel


    【解决方案1】:

    我终于发现使用此代码块会以错误的格式生成 SQL 命令,像这样调整代码将修复它:

    pub fn handle_big_table_query<T: QueryFragment<Pg>>(this: &Paginated<T>, mut out: AstPass<Pg>) -> QueryResult<()> {
        out.push_sql("SELECT *, count_estimate('select * from article') FROM ");
        if this.is_sub_query {
            out.push_sql("(");
        }
        this.query.walk_ast(out.reborrow())?;
        if this.is_sub_query {
            out.push_sql(" LIMIT ");
            out.push_bind_param::<BigInt, _>(&this.per_page)?;
            out.push_sql(" OFFSET ");
            let offset = (this.page - 1) * this.per_page;
            out.push_bind_param::<BigInt, _>(&offset)?;
            out.push_sql(") t");
        }
        Ok(())
    }
    

    【讨论】:

      猜你喜欢
      • 2022-01-20
      • 1970-01-01
      • 2021-07-25
      • 2020-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多