如何与node.js和JavaScript无缝整合以进行数据库监视
#javascript #postgres #sql #database

Object-Relational Mapping (ORM)通过简化关系数据库及其应用程序代码之间的相互作用,证明了开发人员的宝贵价值。通过将数据库交互化为高级编程结构,ORM像Sequelize这样的ORM系统减轻了开发人员编写复杂且容易出错的SQL查询的需求。这种简化可加速发展周期,并降低了SQL注入漏洞的潜力。此外,ORM促进了平台独立性,允许开发人员在不重写大量代码的情况下无缝切换不同的数据库管理系统。

续集为开发人员提供了与数据库交互的无缝和高效手段。用打字稿编写的,为运行查询提供了易于使用的API。虽然其API的构造更像是查询构建器,但它仍然可以帮助开发人员使用完整的类型安全编写查询。它支持多个数据库引擎,使能够逃脱手工编写的查询,处理参数并与其余的生态系统集成。

在这篇博客文章中,我们将使用semelize探索IMDB dataset。我们将在周围建立一个适当的Database Monitoring,以查看性能的变化以及如何调查查询。我们将使用Express和一个PostgreSQL数据库编写JavaScript的应用程序。所有代码都在GitHub中。让我们开始。

应用结构

我将在Amazon Linux 2上实现该应用程序,但是您应该能够在任何操作系统上运行它。

让我们从Github下载初始代码开始。分叉repository和Checkout Tag step_1

git checkout Step_1

商业逻辑

让我们探索the revision。入口点在main.js文件中。它加载环境变量,启动应用程序并将服务器公开 localhost 3000

require('dotenv').config();

const app = require('./app');

async function bootstrap() {
  app.listen(process.env.PORT || 3000, '127.0.0.1');
}

bootstrap();

服务器是在app.js中配置的。我们配置了记录器,CORS,加载控制器,并处理错误。

const express = require('express');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const cors = require('cors');
const controllers = require('./controllers');

function bootstrap(){
  const router = express.Router();
  controllers.initialize(router);

  const app = express();
  app.use(logger('dev'));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());
  app.use(cors())

  app.use('/', router);  

  app.use(function(req, res, next) {
    res.status(404).send({ error: 'Not found' })
  });

  app.use(function(err, req, res, next) {
    console.log("Error: " + err);
    res.locals.message = err.message;
    res.locals.error = err;
    res.status(err.status || 500).send({ error: err })
  });

  return app;
}

module.exports = bootstrap();

我们配置了two controllersThe first one仅公开了一种获得最佳标题并处理结果的方法:

const titleRatingService = require('./title_ratings.service');

module.exports = {
  initialize(router) {
    router.get('/titles/ratings/best', this.handleResult(this.getBestMovies));
  },

  getBestMovies(req, res) {
    return titleRatingService.getBestMovies();
  },

  handleResult(lambda) {
    return (req, res) => Promise.resolve(lambda(req, res))
      .then((results) => res.status(200).send(results))
      .catch((error) => { console.log(error); res.status(400).send(error); });
  }
};

The second one揭示了更多的方法来获取演员,名称,船员和其他有趣的东西。

两个控制器都由还没有返回任何数据的服务支持。您可以看到它们herehere。我们还定义了一组测试,这些测试验证应用程序是否最终工作。 The test suite在所有端点上迭代,一个一个拨打它们,并检查是否收到HTTP代码 200 表示成功:

require('dotenv').config();
const request = require('supertest');

describe('AppController (e2e)', function (){
  this.timeout(0);

  const endpoints = [
    '/titles/ratings/best',
    '/titles?title=Test',
    '/titlesForAnActor?nconst=nm1588970',
    '/highestRatedMoviesForAnActor?nconst=nm1588970',
    '/highestRatedMovies?numvotes=10000',
    '/commonMoviesForTwoActors?actor1=nm0302368&actor2=nm0001908',
    '/crewOfGivenMovie?tconst=tt0000439',
    '/mostProlificActorInPeriod?startYear=1900&endYear=1912',
    '/mostProlificActorInGenre?genre=Action',
    '/mostCommonTeammates?nconst=nm0000428',
  ];

  let app;

  before(async function() {
    this.timeout(0);

    app = require('../app');
  });

  endpoints.map(url => it(`${url} (GET)`, async function() {
    await request(app)
      .get(url)
      .expect(200);
  }));
});

让我们现在查看数据源。

IMDB数据库

我们将使用IMDB数据集。您可以下载文件并将其转换为数据库,但最简单的方法是乘坐已经可以做到这一点的Docker容器。克隆此存储库并运行以下脚本:

./start-docker-database.sh

这应该在本地运行数据库,并将端口5432公开到Local主机。

Image description

启动并连接到数据库后,您会看到某些列未归一化。例如,title_crew具有这一行:

+------------+--------------------------------+---------------------+
|  tconst    |           directors            |       writers       |
+------------+--------------------------------+---------------------+
| tt0000247  | nm2156608,nm0005690,nm0002504  | nm0000636,nm0002504 |
+------------+--------------------------------+---------------------+

加入数据时,我们需要通过逗号分开这些值并进行更多处理。

IMDB数据集中有两个重要的标识符:tconstand nconsttconst用于指示标题。 nconst是一个名字(演员,导演,作家等)。我们将使用这些标识符有效地加入表。

数据集有数百万行,因此我们可以使用它来轻松分析性能。让我们继续。

启动应用程序

包的根目录中有多个脚本,使您可以在本地运行事物。该应用程序可以使用您的本地节点安装或在Docker容器中运行,因此您不需要在本地安装任何内容。

让我们在本地启动应用程序。 build-and-run.sh将在本地编译该应用程序,并将其公开在默认端口3000

Image description

运行脚本后,您可以验证它是否正常使用:

curl http://localhost:3000/titles/ratings/best 

这应该返回一个空数组:

[]

您也可以运行test.sh,该test.sh将使用​​mocha在本地运行所有端点测试:

Image description

您还可以使用start-service.shstart-service.ps1在Docker中启动应用程序。您可以像以前一样使用curl对其进行测试。您可以随后使用remove-container.shremove-container.ps1删除Docker。

此时我们的应用程序可以使用。现在,让它与续集整合。

与续集的集成

我们现在要安装续集。您可以在本地运行所有命令(假设您已配置了节点),也可以检查Step_2标签并查看其工作原理(尤其是如果您没有节点并且只想在Docker中运行内容)。

让s install seltimize如其Getting Started guide中所述:

npm install --save sequelize
npm install --save pg pg-hstore

这些命令将软件包添加到package.json文件中。现在,我们可以使用续集库访问数据库。

让我们从定义实体开始。让我们创建具有以下内容的src/names/entities/name_basic.entity.js

'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class NameBasic extends Model {
    static associate(models) {
    }
  }
  NameBasic.init(
    {
      nconst: { type: DataTypes.STRING, primaryKey: true, allowNull: false },
      primaryname: { type: DataTypes.TEXT },
      birthyear: { type: DataTypes.INTEGER },
      deathyear: { type: DataTypes.INTEGER },
      primaryprofession: { type: DataTypes.TEXT },
      knownfortitles: { type: DataTypes.TEXT }
    },
    {
      sequelize,
      modelName: 'NameBasic',
      tableName: 'name_basics',
      schema: 'imdb',
      timestamps: false,
    }
  );
  return NameBasic;
};

我们导入续集,然后创建一个扩展Model的类NameBasic。我们定义了nconstprimaryname之类的字段,并将模型名称定义为NameBasic。我们可以以相同的方式进行定义other entities

接下来,我们需要在加载模块时初始化续集。让我们创建src/models/index.js

'use strict';

const Sequelize = require('sequelize');
const process = require('process');
const db = {};
const fsPromises = require('fs').promises;

let sequelize = new Sequelize(process.env['DATABASE_URL'], { dialect: 'postgres' });

let models = [
  '../titles/entities/title_rating.entity',
  '../titles/entities/title_basic.entity',
  '../titles/entities/title_principal.entity',
  '../titles/entities/title_crew.entity',
  '../names/entities/name_basic.entity',
];

(function createModels() {
  models.forEach((file) => {
    const model = require(file)(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

  Object.keys(db).forEach((modelName) => {
    if (db[modelName].associate) {
      db[modelName].associate(db);
    }
  });
})();

(function createRelations() {
  (function joinTitleBasiclAndTitlePrincipal(){
    db.TitleBasic.hasMany(db.TitlePrincipal, {
      foreignKey: 'tconst',
      targetKey: 'tconst',
      as: 'titleBasicTitlePrincipal',
    });

    // Another association to allow for two joins when taking movies for two actors
    db.TitleBasic.hasMany(db.TitlePrincipal, {
      foreignKey: 'tconst',
      targetKey: 'tconst',
      as: 'titleBasicTitlePrincipal2',
    });

    db.TitlePrincipal.belongsTo(db.TitleBasic, {
      foreignKey: 'tconst',
      targetKey: 'tconst'
    });
  })();

  (function joinTitlePrincipalAndNameBasic() {
    db.TitlePrincipal.hasOne(db.NameBasic, {
      foreignKey: 'nconst',
      targetKey: 'nconst',
      sourceKey: 'nconst'
    });

    db.NameBasic.belongsTo(db.TitlePrincipal, {
      foreignKey: 'nconst',
      targetKey: 'nconst',
      sourceKey: 'nconst'
    });
  })();

  (function joinTitleRatingAndTitleBasic() {
    db.TitleBasic.hasOne(db.TitleRating, {
      foreignKey: 'tconst',
      targetKey: 'tconst'
    });

    db.TitleRating.belongsTo(db.TitleBasic, {
      foreignKey: 'tconst',
      targetKey: 'tconst'
    });
  })();

  (function joinTitleBasicAndTitleCrew(){
    db.TitleBasic.hasOne(db.TitleCrew, {
      foreignKey: 'tconst',
      targetKey: 'tconst',
    });

    db.TitleCrew.belongsTo(db.TitleBasic, {
      foreignKey: 'tconst',
      targetKey: 'tconst'
    });
  })();
})();

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

我们创建了一个新的quelize实例,该实例与从环境变量获得的连接字符串连接到数据库。环境变量是从.env文件和连接字符串指向本地数据库的加载。

接下来,我们加载实体,定义表之间的关系,最后设置出口,以便我们可以从该模块外部使用它们。

我们现在可以开始使用semelize。让我们修改title_ratings.service.js并提取一些最佳电影:

const titleRating = require('../models').TitleRating;

module.exports = {
  getBestMovies() {
    return titleRating
      .findAll({
        where: {
          averagerating: 10.0
        }
      });
  }
};

您可以看到我们导入TitleRating类,然后使用findAll方法用10星过滤电影。现在,您可以启动应用程序并使用curl验证端点现在返回数据。您还可以重新运行测试以查看所有工作,并且获得最佳标题的终点是发送SQL查询:

Image description

续集编写查询

我们现在可以添加更多查询来执行一些更复杂的逻辑。就像以前一样,您可以手动修改代码,或结帐标签Step_3。所有更改均在this commit中。

转到src/titles/titles.service.js并在文件顶部添加以下导入:

const { Op } = require("sequelize");
const sequelize = require('../models').sequelize;
const titleBasic = require('../models').TitleBasic;
const titlePrincipal = require('../models').TitlePrincipal;
const titleRating = require('../models').TitleRating;
const titleCrew = require('../models').TitleCrew;
const nameBasic = require('../models').NameBasic;

现在,让我们实现以其名称获取所有标题的方法:

getTitles(title) {
   return titleBasic 
     .findAll({
       where: {
         primarytitle: { [Op.like]: '%' + title + '%' }
       }
     });
 },

您可以看到查询的结构相同。我们使用findAll方法,但是这次我们需要以不同的方式配置过滤器。我们想使用将包含操作的LIKE操作员。

现在,让我们实施一种方法来获取给定演员的所有标题:

titlesForAnActor(nconst) {
   function titlesForAnActorNaive() {
     return titleBasic 
       .findAll({
         include: [{
           model: titlePrincipal,
           required: true,
           as: 'titleBasicTitlePrincipal',
           where: {
             'nconst': nconst
           },
         }],
         order: [
          ['startyear', 'DESC']
         ],
         limit: 10
       });
   }

   return titlesForAnActorNaive();
 },

我们拿桌子title_basic,然后使用tconst列与title_principal一起加入。接下来,我们根据nconst标识符过滤,最后对startyear进行对结果进行排序,以首先获得最新的电影。我们返回其中的十个。

同样,我们可以为演员获得最高评价的电影:

highestRatedMovies(numvotes) {
   function highestRatedMoviesNaive() {
     return titleBasic 
       .findAll({
         include: [
           {
             model: titleRating,
             required: true,
             duplicating: false,
             where: {
               'numvotes': { [Op.gte]: numvotes }
             }
           },
         ],
         order: [
           [ titleRating, 'averagerating', 'DESC'], 
         ]
       });
   }

   return highestRatedMoviesNaive();
 },

我们使用title_rating加入title_basic,进行过滤,最后我们根据电影的星级订购结果。

续集非常强大,但是有时直接实现SQL查询更容易。我们可以这样做才能让电影的工作人员:

crewOfGivenMovie(tconst) {
   function crewOfGivenMovieManualSlow(){
       return sequelize.query(`
         SELECT DISTINCT NB.*
         FROM imdb.title_basics AS TB
         LEFT JOIN imdb.title_principals AS TP ON TP.tconst = TB.tconst
         LEFT JOIN imdb.title_crew AS TC ON TC.tconst = TB.tconst
         LEFT JOIN imdb.name_basics AS NB ON 
                 NB.nconst = TP.nconst 
                 OR TC.directors = NB.nconst
                 OR TC.directors LIKE NB.nconst || ',%'::text
                 OR TC.directors LIKE '%,'::text || NB.nconst || ',%'::text
                 OR TC.directors LIKE '%,'::text || NB.nconst
                 OR TC.writers = NB.nconst
                 OR TC.writers LIKE NB.nconst || ',%'::text
                 OR TC.writers LIKE '%,'::text || NB.nconst || ',%'::text
                 OR TC.writers LIKE '%,'::text || NB.nconst
         WHERE TB.tconst = :tconst
       `, {
         model: nameBasic,
         mapToModel: true,
         replacements: {
           tconst: tconst
         }
     });
   }

   return crewOfGivenMovieManualSlow();
 },

如前所述,IMDB中的某些列存储为CSV值。为了找到CSV的特定组件,我们需要通过逗号将其拆分并进行一些模式匹配。后来,我们将以不同的方式看待如何做同样的事情。

让我们现在运行所有测试,看看它们发送了实际查询并获取更多数据:

Image description

引入数据库监视和可观察性

我们的应用程序有效,但是我们不知道它的作用以及它产生了什么查询。我们不知道部署到生产时是否会足够快。我们需要查看幕后,以确保一切正常。

我们将与METIS集成该应用程序以构建数据库监视。这样,我们将能够在开始生产之前看到性能问题和optimize our queries

继续前进,请转到Metis并在那里创建一个项目。您将需要一个API键来集成应用程序。

您可以看到标签Step_4中的所有更改。正如它所描述的in the documentation所描述的那样,我们将实施该仪器。让我们从安装依赖项开始。 METIS使用OpenTelemetry提取其余呼叫和SQL查询的详细信息。由于METIS仅捕获执行计划,因此没有将机密数据发送到平台。

npm install --save pg @metis-data/pg-interceptor
npm install --save @opentelemetry/api \
                @opentelemetry/context-async-hooks \
                @opentelemetry/instrumentation \
                @opentelemetry/instrumentation-http \
                @opentelemetry/resources \
                @opentelemetry/sdk-trace-base \
                @opentelemetry/semantic-conventions

完成此操作后,我们需要添加跟踪配置。添加文件src/tracer.js

let opentelemetry = require('@opentelemetry/api');
let { registerInstrumentations } = require('@opentelemetry/instrumentation');
let {
  BasicTracerProvider,
  BatchSpanProcessor,
  ConsoleSpanExporter,
  SimpleSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
let { getMetisExporter, getResource, MetisHttpInstrumentation, MetisPgInstrumentation } = require('@metis-data/pg-interceptor');
let { AsyncHooksContextManager } = require('@opentelemetry/context-async-hooks');

let tracerProvider;
let metisExporter;

const shudownhook = async () => {
  console.log('Shutting down tracer provider and exporter...');
  await tracerProvider?.shutdown();
  await metisExporter?.shutdown();
  console.log('Tracer provider and exporter were shut down.');
}

process.on('SIGINT', () => {
  shudownhook().finally(() => process.exit(0));
});
process.on('SIGTERM', () => {
  shudownhook().finally(() => process.exit(0));
});

const connectionString = process.env.DATABASE_URL;

const startMetisInstrumentation = () => {
  tracerProvider = new BasicTracerProvider({
    resource: getResource(process.env.METIS_SERVICE_NAME, process.env.METIS_SERVICE_VERSION),
  });

  metisExporter = getMetisExporter(process.env.METIS_API_KEY);

  tracerProvider.addSpanProcessor(new BatchSpanProcessor(metisExporter));

  if (process.env.OTEL_DEBUG === "true") {
    tracerProvider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
  }

  const contextManager = new AsyncHooksContextManager();

  contextManager.enable();
  opentelemetry.context.setGlobalContextManager(contextManager);

  tracerProvider.register();

  const excludeUrls = [/favicon.ico/];
  registerInstrumentations({
    instrumentations: [new MetisPgInstrumentation({ connectionString }), new MetisHttpInstrumentation(excludeUrls)],
  });
};

module.exports = {
  startMetisInstrumentation,
  shudownhook,
};

我们提取METIS API密钥,为Opentelemtry的跨度添加处理器,然后注册PG(SQL驱动程序)和HTTP(网络调用)的仪器。最后,我们需要将环境变量添加到.env文件:

DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/demo?schema=imdb
METIS_API_KEY=YOUR_API_KEY
METIS_SERVICE_NAME=sequelize
METIS_SERVICE_VERSION=1

METIS_API_KEY是您项目的关键。 METIS_SERVICE_NAMEMETIS_SERVICE_VERSION可以是您喜欢的。这两个值仅用于指示应用程序发送的迹线。您可以使用它进行版本操作,区分开发人员和生产版本,或将其与CI/CD Pipeline集成。

所有代码都准备就绪。现在,我们可以启用仪器,以便我们的应用程序使用它。让我们在the main entrypointthe tests拨打startMetisInstrumentation。这样,当我们在本地使用应用程序或运行自动测试套件时,我们可以捕获性能见解。现在,让我们启动应用程序并使用卷曲来测试端点:

curl http://localhost:3000/titles/ratings/best 

这应该返回多个标题。现在,让您转到您的Metis项目,转到最近的活动标签,您应该看到这样的东西:

Image description

您可以看到Metis捕获了呼叫titles/ratings/best端点。我们可以看到端点返回了HTTP代码200。让我们现在单击此执行,我们应该进入此屏幕:

Image description

您可以看到顶部的跨度。它们中有多个,因为续集在运行第一个查询之前最初配置了数据库。最后一个跨度表示我们已执行的实际SQL查询。您可以单击“ SQL”选项卡以查看查询文本:

Image description

您可以看到续集发送的查询!这与我们在日志中看到的查询相同。但是,这次我们可以自动分析它。返回到Insights选项卡,查看以下内容:

Image description

梅蒂斯向我们展示了查询通过扫描整个桌子读取超过一百万行。这是一个关键的问题,因为当我们部署到生产时,它的规模不会很好。让我们修复。

使用数据库监控来改善SQL查询性能

让我们添加索引。该代码位于标签Step_5中。首先,让我们创建一个迁移,在启动应用程序时将执行该迁移。让我们添加src/models/migrations/001_create_title_ratings_indexed.sql

CREATE INDEX IF NOT EXISTS title_ratings_index ON imdb.title_ratings(averagerating);

这将在表上创建一个索引,以确保我们的查询运行速度更快。现在,让我们实施一点代码来运行此迁移。将此方法添加到src/models/index.js

(function seed(){
  db.seedDatabase = async () => {
    let files = await fsPromises.readdir('src/models/migrations/');
    for(let id in files) {
      if(files[id].endsWith(".sql")){
        let data = await fsPromises.readFile('src/models/migrations/' + files[id], 'utf8');
        console.log("Running " + data);
        try{
          console.log(await sequelize.query(data));
        }catch(e){
          console.log(e);
          throw e;
        }
      }
    }

    console.log("Done migrating");
  };
})();

让我们现在在the entrypointin the tests中调用此方法。

让我们现在重新启动应用程序,然后查看以下内容:

Image description

太好了,迁移已执行。现在,我们可以再次运行curl,然后转到Metis Project查看以下内容:

Image description

您可以看到还有另一个呼叫,我们刚刚打了一个电话。让我们打开它,看看查询现在已经足够快:

Image description

您可以看到查询现在读取约七千行。这是一个很好的表现。

改善其他查询

我们现在已经准备好所有的碎片。让我们看看如何使用新的数据库监视和可观察性使用续集来提高性能。我们可以在这里进行多种改进,所以我只会显示其中的一些。请参阅tag Step_6查看更多信息并阅读我们的文章How Metis Optimized Queries Executed by Sequelize以获取更多想法。

让我们重新访问演员的查询返回电影。用curl进行运行:

curl http://localhost:3000/titlesForAnActor?nconst=nm1588970

转到梅蒂斯(Metis),看看呼叫已被捕获:

Image description

存在关键问题。让我看看他们:

Image description

我们可以看到查询读了7000万行。那很多!让我们看看查询文本:

SELECT
  TitleBasic.*,
  titleBasicTitlePrincipal.tconst AS titleBasicTitlePrincipal.tconst,
  titleBasicTitlePrincipal.ordering AS titleBasicTitlePrincipal.ordering,
  titleBasicTitlePrincipal.nconst AS titleBasicTitlePrincipal.nconst,
  titleBasicTitlePrincipal.category AS titleBasicTitlePrincipal.category,
  titleBasicTitlePrincipal.job AS titleBasicTitlePrincipal.job,
  titleBasicTitlePrincipal.characters AS titleBasicTitlePrincipal.characters
FROM
  (
    SELECT
    TitleBasic.tconst,
    TitleBasic.titletype,
    TitleBasic.primarytitle,
    TitleBasic.originaltitle,
    TitleBasic.isadult,
    TitleBasic.startyear,
    TitleBasic.endyear,
    TitleBasic.runtimeminutes,
    TitleBasic.genres
    FROM
    imdb.title_basics AS TitleBasic
    WHERE
    (
        SELECT
        tconst
        FROM
        imdb.title_principals AS titleBasicTitlePrincipal
        WHERE
        (
            titleBasicTitlePrincipal.nconst = 'nm1588970'
            AND titleBasicTitlePrincipal.tconst = TitleBasic.tconst
        )
        LIMIT
        1
    ) IS NOT NULL
    ORDER BY
    TitleBasic.startyear DESC
    LIMIT
    10
  ) AS TitleBasic
  INNER JOIN imdb.title_principals AS titleBasicTitlePrincipal ON TitleBasic.tconst = titleBasicTitlePrincipal.tconst
  AND titleBasicTitlePrincipal.nconst = 'nm1588970'
ORDER BY
  TitleBasic.startyear DESC;

这绝对不是我们期望的。让我们尝试更快地进行索引:

CREATE INDEX IF NOT EXISTS title_principals_nconst_idx ON imdb.title_principals(nconst) INCLUDE (tconst);

您可以添加另一个迁移,重新运行应用程序并看到它有所帮助,但绝对不够。

让我们尝试了解为什么续集生成的如此奇怪的查询。我们加入了两张桌子,根据Startyear订购了行,然后返回了十行。但是,续集不知道连接是否可以创建重复项。在这种情况下,续集需要与子查询一起计算正确的排序。我们可以使用duplicating标志来解决该问题:

return titleBasic
    .findAll({
        include: [{
            model: titlePrincipal,
            required: true,
            duplicating: false,
            as: 'titleBasicTitlePrincipal',
            where: {
                'nconst': nconst
            },
        }],
        order: [
            ['startyear', 'DESC']
        ],
        limit: 10
    });

我们现在可以重新启动应用程序,并看到这是我们现在得到的查询:

SELECT
    TitleBasic.tconst,
    TitleBasic.titletype,
    TitleBasic.primarytitle,
    TitleBasic.originaltitle,
    TitleBasic.isadult,
    TitleBasic.startyear,
    TitleBasic.endyear,
    TitleBasic.runtimeminutes,
    TitleBasic.genres,
    titleBasicTitlePrincipal.tconst AS titleBasicTitlePrincipal.tconst,
    titleBasicTitlePrincipal.ordering AS titleBasicTitlePrincipal.ordering,
    titleBasicTitlePrincipal.nconst AS titleBasicTitlePrincipal.nconst,
    titleBasicTitlePrincipal.category AS titleBasicTitlePrincipal.category,
    titleBasicTitlePrincipal.job AS titleBasicTitlePrincipal.job,
    titleBasicTitlePrincipal.characters AS titleBasicTitlePrincipal.characters
FROM
    imdb.title_basics AS TitleBasic
INNER JOIN imdb.title_principals AS titleBasicTitlePrincipal ON TitleBasic.tconst = titleBasicTitlePrincipal.tconst AND titleBasicTitlePrincipal.nconst = 'nm1588970'
ORDER BY
    TitleBasic.startyear DESC
LIMIT
    10;

一旦添加索引和适合处理重复的标志,我们就会获得以下性能:

Image description

您可以看到现在已经足够了,可以部署到生产中。

概括

在这篇文章中,我们看到了如何使用semelize构建数据库监视。我们看到了如何分析SQL查询性能,如何监视数据库以及如何使用实际IMDB数据集优化应用程序。这样,您就可以在进行生产之前对所有应用程序和查询进行调查。而且,如果这还不够

常问问题

与IMDB数据集一起使用quelize orm的目的是什么?

我们可以简化从JavaScript应用程序读取数据库对象,并以更好的方式运行SQL查询。

如何使用quelize导入和预处理IMDB数据?

我们可以使用公开IMDB实体的Docker数据库。然后,我们可以在javascript应用程序中映射到实体。

IMDB数据集分析的续集的关键功能是什么?

续集使我们可以配置关系并简化我们编写查询的方式。我们可以自动验证类型,列的存在或查询是否要正确执行。