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 controllers。 The 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揭示了更多的方法来获取演员,名称,船员和其他有趣的东西。
两个控制器都由还没有返回任何数据的服务支持。您可以看到它们here和here。我们还定义了一组测试,这些测试验证应用程序是否最终工作。 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主机。
启动并连接到数据库后,您会看到某些列未归一化。例如,title_crew
具有这一行:
+------------+--------------------------------+---------------------+
| tconst | directors | writers |
+------------+--------------------------------+---------------------+
| tt0000247 | nm2156608,nm0005690,nm0002504 | nm0000636,nm0002504 |
+------------+--------------------------------+---------------------+
加入数据时,我们需要通过逗号分开这些值并进行更多处理。
IMDB数据集中有两个重要的标识符:tconst
and nconst
。 tconst
用于指示标题。 nconst
是一个名字(演员,导演,作家等)。我们将使用这些标识符有效地加入表。
数据集有数百万行,因此我们可以使用它来轻松分析性能。让我们继续。
启动应用程序
包的根目录中有多个脚本,使您可以在本地运行事物。该应用程序可以使用您的本地节点安装或在Docker容器中运行,因此您不需要在本地安装任何内容。
让我们在本地启动应用程序。 build-and-run.sh
将在本地编译该应用程序,并将其公开在默认端口3000
:
运行脚本后,您可以验证它是否正常使用:
curl http://localhost:3000/titles/ratings/best
这应该返回一个空数组:
[]
您也可以运行test.sh
,该test.sh
将使用mocha在本地运行所有端点测试:
您还可以使用start-service.sh
或start-service.ps1
在Docker中启动应用程序。您可以像以前一样使用curl
对其进行测试。您可以随后使用remove-container.sh
和remove-container.ps1
删除Docker。
此时我们的应用程序可以使用。现在,让它与续集整合。
与续集的集成
我们现在要安装续集。您可以在本地运行所有命令(假设您已配置了节点),也可以检查Step_2
标签并查看其工作原理(尤其是如果您没有节点并且只想在Docker中运行内容)。 P>
让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
。我们定义了nconst
或primaryname
之类的字段,并将模型名称定义为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查询:
续集编写查询
我们现在可以添加更多查询来执行一些更复杂的逻辑。就像以前一样,您可以手动修改代码,或结帐标签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的特定组件,我们需要通过逗号将其拆分并进行一些模式匹配。后来,我们将以不同的方式看待如何做同样的事情。
让我们现在运行所有测试,看看它们发送了实际查询并获取更多数据:
引入数据库监视和可观察性
我们的应用程序有效,但是我们不知道它的作用以及它产生了什么查询。我们不知道部署到生产时是否会足够快。我们需要查看幕后,以确保一切正常。
我们将与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_NAME
和METIS_SERVICE_VERSION
可以是您喜欢的。这两个值仅用于指示应用程序发送的迹线。您可以使用它进行版本操作,区分开发人员和生产版本,或将其与CI/CD Pipeline集成。
所有代码都准备就绪。现在,我们可以启用仪器,以便我们的应用程序使用它。让我们在the main entrypoint和the tests拨打startMetisInstrumentation
。这样,当我们在本地使用应用程序或运行自动测试套件时,我们可以捕获性能见解。现在,让我们启动应用程序并使用卷曲来测试端点:
curl http://localhost:3000/titles/ratings/best
这应该返回多个标题。现在,让您转到您的Metis项目,转到最近的活动标签,您应该看到这样的东西:
您可以看到Metis捕获了呼叫titles/ratings/best
端点。我们可以看到端点返回了HTTP代码200。让我们现在单击此执行,我们应该进入此屏幕:
您可以看到顶部的跨度。它们中有多个,因为续集在运行第一个查询之前最初配置了数据库。最后一个跨度表示我们已执行的实际SQL查询。您可以单击“ SQL”选项卡以查看查询文本:
您可以看到续集发送的查询!这与我们在日志中看到的查询相同。但是,这次我们可以自动分析它。返回到Insights选项卡,查看以下内容:
梅蒂斯向我们展示了查询通过扫描整个桌子读取超过一百万行。这是一个关键的问题,因为当我们部署到生产时,它的规模不会很好。让我们修复。
使用数据库监控来改善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 entrypoint和in the tests中调用此方法。
让我们现在重新启动应用程序,然后查看以下内容:
太好了,迁移已执行。现在,我们可以再次运行curl
,然后转到Metis Project查看以下内容:
您可以看到还有另一个呼叫,我们刚刚打了一个电话。让我们打开它,看看查询现在已经足够快:
您可以看到查询现在读取约七千行。这是一个很好的表现。
改善其他查询
我们现在已经准备好所有的碎片。让我们看看如何使用新的数据库监视和可观察性使用续集来提高性能。我们可以在这里进行多种改进,所以我只会显示其中的一些。请参阅tag Step_6查看更多信息并阅读我们的文章How Metis Optimized Queries Executed by Sequelize以获取更多想法。
让我们重新访问演员的查询返回电影。用curl
进行运行:
curl http://localhost:3000/titlesForAnActor?nconst=nm1588970
转到梅蒂斯(Metis),看看呼叫已被捕获:
存在关键问题。让我看看他们:
我们可以看到查询读了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;
一旦添加索引和适合处理重复的标志,我们就会获得以下性能:
您可以看到现在已经足够了,可以部署到生产中。
概括
在这篇文章中,我们看到了如何使用semelize构建数据库监视。我们看到了如何分析SQL查询性能,如何监视数据库以及如何使用实际IMDB数据集优化应用程序。这样,您就可以在进行生产之前对所有应用程序和查询进行调查。而且,如果这还不够
常问问题
与IMDB数据集一起使用quelize orm的目的是什么?
我们可以简化从JavaScript应用程序读取数据库对象,并以更好的方式运行SQL查询。
如何使用quelize导入和预处理IMDB数据?
我们可以使用公开IMDB实体的Docker数据库。然后,我们可以在javascript应用程序中映射到实体。
IMDB数据集分析的续集的关键功能是什么?
续集使我们可以配置关系并简化我们编写查询的方式。我们可以自动验证类型,列的存在或查询是否要正确执行。