簡體   English   中英

NestJS:使用 mssql 執行原始 SQL 查詢。 太慢了。 配置問題?

[英]NestJS: Raw SQL query execution with mssql. Too Slow. Configuration problems?

Nestjs 中連接到 SQL 服務器數據庫的 APP。 所有查詢都寫在數據庫端,因此它們之間的連接是使用簡單的原始 SQL 和使用 mssql package。

事情是這樣的:當我在 SSMS 中運行時,會在幾毫秒內執行一個非常小的查詢(比如說 <20 條記錄)。 (甚至更大更復雜的查詢或 SP 具有良好的性能)當我在應用程序中運行時,使用本地數據庫連接,查詢開始有一些延遲(讓我們說 1 秒對於相同的查詢)但是當我開始使用 Azure 上的數據庫時,同樣的小查詢需要 3 到 5 秒(對於 20 條記錄

我讀到一些原因可能與參數嗅探有關,但我不認為是這樣。 我猜,每次新查詢到達時,我的后端都會重新啟動數據庫連接。

這是應用程序的邏輯:控制器使用的一個集中式 CRUD 服務。

在 main.ts 是連接:

  async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const logger = new Logger('Bootstrap', { timestamp: true });
  const configService = app.get(ConfigService);

  
  //Database configuration
  const sqlConfig = {
    user:
      configService.get('DB_USELOCAL') === 'false'
        ? configService.get('DB_USERNAME')
        : configService.get('DB_USERNAME_LOCAL'),
    password:
      configService.get('DB_USELOCAL') === 'false'
        ? configService.get('DB_PASSWORD')
        : configService.get('DB_PASSWORD_LOCAL'),
    server:
      configService.get('DB_USELOCAL') === 'false'
        ? configService.get('DB_SERVER')
        : configService.get('DB_SERVER_LOCAL'),
    database:
      configService.get('DB_USELOCAL') === 'false'
        ? configService.get('DB_DATABASE')
        : configService.get('DB_DATABASE_LOCAL'),
    pool: {
      max: 10,
      min: 0,
      idleTimeoutMillis: 30000,
    },
    requestTimeout: 180000, //3 minutes to wait for a request to the database.
    options: {
      //encrypt: false, // for azure
      encrypt: configService.get('DB_USELOCAL') === 'false' ? true : false,
      trustServerCertificate: false, // change to true for local dev / self-signed certs
    },
  };
  sql.connect(sqlConfig);
  logger.log('App connected to database MSSQL');

  //CORS: Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain.
  app.enableCors();

  //App running
  await app.listen(configService.get('PORT') || 3000);
  logger.log(`App running on port ${configService.get('PORT') || 3000}`);
}
bootstrap();

在 CRUD 服務中請求的查詢

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { fxSQLerrorMsg } from './function/SLQerrorMsg.fx';
import * as sql from 'mssql';
import { FxArrayObjectStr } from './function/arrayObjectStr.fx';
import { FxObjectStr } from './function/objectStr.fx';
import { FindBodyDTO } from './findBody.dto';

@Injectable()
export class CrudService {
  private logger = new Logger('Crud Service', { timestamp: true });

  async find(
    sp: string,
    DB: string,
    body?: FindBodyDTO | null,
    query?: Record<string, any> | null,
    email?: string,
    filter?: string,
  ): Promise<Record<string, any>[]> {
    const method = "'" + 'find' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = body
      ? "'" + JSON.stringify(body).replace('%20', ' ') + "'"
      : null;
    const queryParam = FxObjectStr(query);
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ',' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);

    try {
      return (await sql.query<Record<string, any>[]>(spScript))
        .recordset as unknown as Record<string, any>[];
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Find'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async post(
    sp: string,
    DB: string,
    body: Record<string, any>[],
    email?: string,
    filter?: string,
  ): Promise<Record<string, string>> {
    const method = "'" + 'post' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = FxArrayObjectStr(body);
    const queryParam = null;
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ', ' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);
    try {
      return (
        (await sql.query<string>(spScript)).recordset as any[]
      )[0] as Record<string, string>;
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Post'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async updateOne(
    sp: string,
    DB: string,
    body: Record<string, any>[],
    query?: Record<string, any>,
    email?: string,
    filter?: string,
  ): Promise<Record<string, string>> {
    const method = "'" + 'updateOne' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = FxArrayObjectStr(body);
    const queryParam = FxObjectStr(query);
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ', ' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);
    try {
      return (
        (await sql.query<string>(spScript)).recordset as any[]
      )[0] as Record<string, string>;
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Update'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async updateMany(
    sp: string,
    DB: string,
    body: Record<string, any>[],
    query?: Record<string, any>,
    email?: string,
    filter?: string,
  ): Promise<Record<string, string>> {
    const method = "'" + 'updateMany' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = FxArrayObjectStr(body);
    const queryParam = FxObjectStr(query);
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ', ' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);
    try {
      return (
        (await sql.query<string>(spScript)).recordset as any[]
      )[0] as Record<string, string>;
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Update'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async deleteOne(
    sp: string,
    DB: string,
    query?: Record<string, any>,
    email?: string,
    filter?: string,
  ): Promise<Record<string, string>> {
    const method = "'" + 'deleteOne' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = null;
    const queryParam = FxObjectStr(query);
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ', ' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);
    try {
      return (
        (await sql.query<string>(spScript)).recordset as any[]
      )[0] as Record<string, string>;
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Delete'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }

  async deleteMany(
    sp: string,
    DB: string,
    body: Record<string, any>[],
    query: Record<string, any>,
    email?: string,
    filter?: string,
  ): Promise<Record<string, string>> {
    const method = "'" + 'deleteMany' + "'";
    const storeProcedure =
      process.env.SPECIFYDB == 'true'
        ? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
        : 'EXECUTE [ml_sp].[' + sp + ']';
    const bodyParam = FxArrayObjectStr(body);
    const queryParam = FxObjectStr(query);
    const emailScript = email ? "'" + email + "'" : null;
    const filterScript = filter ? "'" + filter + "'" : null;
    const spScript =
      storeProcedure +
      ' ' +
      method +
      ', ' +
      bodyParam +
      ', ' +
      queryParam +
      ', ' +
      emailScript +
      ',' +
      filterScript;
    this.logger.verbose(spScript);
    try {
      return (
        (await sql.query<string>(spScript)).recordset as any[]
      )[0] as Record<string, string>;
    } catch (error) {
      this.logger.error(error);
      throw new HttpException(
        fxSQLerrorMsg(error.message, 'Delete'),
        HttpStatus.BAD_REQUEST,
      );
    }
  }
}

附加信息:我正在測試的查詢(我稱之為非常小的查詢)是:

ALTER VIEW [ml_view].[User2Role] AS 
(SELECT [ml_users].[User2Role].[id] as [id],
[User_user_Aux].[email] as [user],
[PortfolioRole_portfoliorole_Aux].[name] as [portfoliorole],
[ml_users].[User2Role].[editiondate] as [editiondate],
[User_editedbyuser_Aux].[email] as [editedbyuser] 
FROM [ml_users].[User2Role]
LEFT JOIN [ml_users].[User] as [User_user_Aux] ON [User_user_Aux].[id] = [ml_users].[User2Role].[userid] 
LEFT JOIN [ml_setup].[PortfolioRole] as [PortfolioRole_portfoliorole_Aux] ON [PortfolioRole_portfoliorole_Aux].[id] = [ml_users].[User2Role].[portfolioroleid] 
LEFT JOIN [ml_users].[User] as [User_editedbyuser_Aux] ON [User_editedbyuser_Aux].[id] = [ml_users].[User2Role].[editedbyuser])

實際上,它是作為視圖存儲的,並通過一個 SP 運行。 但是我們測試了直接執行視圖(Select * from [viewName]),結果是一樣的。

解決方案:問題不在於SQL服務器的NestJS配置,而恰恰在於Azure中分配給數據庫的資源(DTU或vCores)

接下來我總結了我們采取的一些行動,但最后,這是一個愚蠢的錯誤。 但是,猜測對於保留帖子很有用。

  • 數據嗅探控制。
  • 重建索引。
  • 重置統計信息。
  • 在 Nestjs 中嘗試不同的 SQL 配置(typeorm、乏味等)
  • 將資源分配給(DTU 或 Vcore 模型)。 請參閱 Azure“計算 + 存儲”部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM