资讯中心

MikroORM 7 索引与唯一约束完全指南:从基础装饰器到高级数据库特性

📅 2026/9/28 20:52:11
MikroORM 7 索引与唯一约束完全指南:从基础装饰器到高级数据库特性
后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载MikroORM 提供了对实体索引Index与唯一约束Unique Constraint的全方位支持本文以 MikroORM 7 版本文档为骨架围绕Index()/Unique()装饰器展开覆盖从最基础的单列/复合索引定义到表达式索引、覆盖索引、填充因子、隐形索引、聚集索引、可延迟唯一约束等高级数据库特性并结合仓库源码与测试用例说明其底层实现与适用边界。读完本文你将能够为任意实体精准地声明符合目标数据库能力的索引与约束并理解它们如何被 Schema Generator 落地为真实的 DDL 语句。基础索引与唯一约束定义在 MikroORM 中索引通过Index()装饰器定义唯一约束通过Unique()装饰器定义。两者既可以施加在实体类上用于声明复合索引/复合唯一约束也可以施加在属性上用于声明单列索引/单列唯一约束。Entity() Index({ properties: [name, age] }) // compound index Index({ name: custom_idx_name, properties: [name] }) // named index Unique({ properties: [name, email] }) // compound unique constraint export class Author { PrimaryKey() id!: number; Property() Index() // simple index with generated name name!: string; Property() Unique() // simple unique constraint email!: string; Property() Index({ name: age_idx }) // named index age?: number; }上述示例同时适用于 reflect-metadata 与 ts-morph 两种元数据发现方式两种方式下的装饰器写法完全一致区别仅在项目的元数据提供者配置上可参见 using-decorators.md。装饰器底层如何工作从源码看Index()与Unique()本质上是同一个createDecorator(options, unique)工厂函数的分支见 packages/decorators/src/legacy/Indexed.ts当装饰器施加在属性上时propertyName存在会自动把options.properties兜底为当前属性名options.properties ?? propertyName因此Index()直接写在属性上即可生成该列的索引当装饰器施加在实体类上时propertyName为空则显式使用options.properties中的属性列表最终所有选项被推入实体的元数据数组meta.indexes普通索引或meta.uniques唯一约束。这也解释了为什么“复合索引必须写在实体类上”——只有实体级装饰器才能同时引用多个属性。此外 MikroORM 7 还同时提供了 TC39 标准装饰器packages/decorators/src/es/Indexed.ts它支持ClassDecoratorContext/ClassFieldDecoratorContext并在字段级自动将当前字段名写入options.properties。命名规则不传name时索引名由命名策略自动生成。SQL 驱动的默认格式形如{table}_{column1}_{column2}_idx复合索引会拼接多个列名具体规则可参考各平台实现显式传入name可以完全自定义索引名这在需要与既有 DBA 命名规范对齐时非常有用。自定义索引表达式Custom Index Expressions对于需要手写复杂 SQL 的场景可以使用expression选项直接指定完整的CREATE INDEX语句。expression支持两种形式原始 SQL 字符串以及接收(table, columns, indexName)三个参数的表达式回调。Entity() export class Author { // Raw SQL expression Index({ name: custom_index_expr, expression: alter table author add index custom_index_expr(title) }) Property() title!: string; // Expression callback with table and column references Index({ name: custom_index_country, expression: (table, columns, indexName) create index \${indexName}\ on \${table.name}\ (\${columns.country}\) }) Property() country!: string; }回调签名的类型定义在 packages/core/src/typings.tsexport type IndexCallbackT ( columns: RecordPropertyNameT, string, table: SchemaTable, indexName: string, ) string | Raw;其中columns对象将属性名映射到实际数据库列名table.name提供表名含模式信息indexName是你在选项中声明的索引名。这种回调形式的好处是即使实体的命名策略发生变化例如列名从country变成country_code生成的 SQL 仍会引用正确的列名。需要注意的一点是expression是“逃生舱”式设计它会绕过 MikroORM 对索引定义的常规处理因此 Schema Generator 不会再去解析表达式中的列。如果回调使用了函数形式还需要留意元数据缓存——函数表达式无法通过 JSON 序列化保存MikroORM 在缓存时会单独维护一个expressionMap以保留函数引用见 packages/core/src/metadata/MetadataProvider.ts这意味着启用元数据缓存时函数形式依然可用但底层实现比字符串形式更复杂。索引类型Index Types通过type选项可以指定索引类型常用于全文索引fulltext、空间索引spatial以及数据库特有的hash/btree等类型// Fulltext index (MySQL, PostgreSQL, MongoDB) Index({ properties: [content], type: fulltext }) // Spatial index Index({ properties: [location], type: spatial }) // Hash index (PostgreSQL) Index({ properties: [lookup_key], type: hash })从源码看type仅存在于IndexOptions普通索引而不存在于UniqueOptions——唯一约束没有“类型”概念见 packages/core/src/metadata/types.ts。Schema Generator 在生成 DDL 时会把该类型拼接到CREATE INDEX语句中在 PostgreSQL 中btree是默认类型因此反向解析时只有非btree的类型才会被记录进索引定义见 packages/sql/src/dialects/postgresql/PostgreSqlSchemaHelper.ts。列的排序方向与 NULLS 排序使用columns选项可以逐列指定索引内的排序方向ASC/DESC以及 NULL 值的排列位置NULLS FIRST/NULLS LASTEntity() Index({ properties: [createdAt, name], columns: [ { name: createdAt, sort: DESC, nulls: LAST }, { name: name, sort: ASC }, ], }) export class Article { PrimaryKey() id!: number; Property() name!: string; Property() createdAt!: Date; }IndexColumnOptions的完整字段定义见 packages/core/src/metadata/types.ts每个条目支持name、sort、nulls、length、collation五个维度。sort默认值为ASC。数据库支持范围排序方向ASC/DESCMySQL、MariaDB、PostgreSQL、MSSQL、SQLiteNULLS FIRST/LAST仅 PostgreSQL。值得说明的是虽然 SQLite 与 MySQL 等方言不直接支持NULLS FIRST/LAST语法但 MikroORM 的columns选项会在这些平台上自动降级处理而 PostgreSQL 原生支持该语法其反向解析逻辑可以从pg_get_indexdef返回的完整表达式里提取每个列的sort与nulls修饰符见 PostgreSqlSchemaHelper.ts。列前缀长度Column Prefix Length对于TEXT等大文本字段MySQL / MariaDB 允许只对字段的前 N 个字符建立索引从而大幅减小索引体积。MikroORM 通过columns条目中的length选项表达这一需求Entity() Index({ properties: [content], columns: [{ name: content, length: 100 }], }) export class Article { PrimaryKey() id!: number; Property({ type: text }) content!: string; }数据库支持范围MySQL、MariaDB。列排序规则Column Collation索引列的 collation排序规则会影响索引内部对字符串的比较方式适用于需要特定排序语义如大小写敏感比较的场景Entity() Index({ properties: [name], columns: [{ name: name, collation: C }], }) export class User { PrimaryKey() id!: number; Property() name!: string; }数据库支持范围PostgreSQL、SQLite、MySQL、MariaDB。需要说明的是在 MySQL/MariaDB 上 collation 通常需要借助表达式索引才能生效源码注释中注明 “PostgreSQL, SQLite, or MySQL/MariaDB via expression”见 IndexColumnOptions 定义。覆盖索引Covering IndexesINCLUDE 列覆盖索引会把额外列存储进索引的叶子页使得查询可以“只走索引”而不必回表访问数据行。MikroORM 用include选项声明这些附加列Entity() Index({ properties: [email], include: [name, createdAt], // these columns are stored in the index }) export class User { PrimaryKey() id!: number; Property() email!: string; Property() name!: string; Property() createdAt!: Date; }这将生成类似如下的 SQL-- PostgreSQL CREATE INDEX user_email_idx ON user (email) INCLUDE (name, created_at) -- MSSQL CREATE INDEX [user_email_idx] ON [user] ([email]) INCLUDE ([name], [created_at])数据库支持范围PostgreSQL、MSSQL。源码中 PostgreSQL 方言的反向解析会先从pg_get_indexdef的表达式里提取include (...)部分再将其从索引键列中过滤掉确保 INCLUDE 列不会与键列混淆见 PostgreSqlSchemaHelper.ts。填充因子Fill FactorfillFactor指定索引每个页面中被数据填充的百分比剩余空间预留给未来的页增长适合频繁更新的高基数字段Entity() Index({ properties: [email], fillFactor: 70, // 70% fill, 30% free space }) export class User { PrimaryKey() id!: number; Property() email!: string; }数据库支持范围PostgreSQL、MSSQL。在源码层面fillFactor的取值区间是0–100Schema Generator 在收集索引定义时会做显式校验超出该区间会直接抛出错误见 packages/sql/src/schema/DatabaseTable.tsif (index.fillFactor ! null (index.fillFactor 0 || index.fillFactor 100)) { throw new Error( fillFactor must be between 0 and 100, got ${index.fillFactor} for index ${name} on entity ${meta.className}, ); }PostgreSQL 反向解析时则从索引的reloptions中提取fillfactor前缀的值见 PostgreSqlSchemaHelper.ts。隐形/隐藏索引Invisible Indexes隐形索引invisible index仍然会在写入时被数据库维护但不会被查询优化器使用。这一特性非常适合在真正DROP INDEX之前先验证删除某个索引对线上查询性能的实际影响Entity() Index({ properties: [name], invisible: true, }) export class User { PrimaryKey() id!: number; Property() name!: string; }数据库支持范围MySQL 8.0、MariaDB 10.6、MongoDB。需要区分的是invisible只存在于IndexOptions中唯一约束不支持该选项且它是“索引专属”选项——DatabaseTable在处理唯一索引时会跳过type、invisible、clustered等字段见 packages/sql/src/schema/DatabaseTable.ts。在 MySQL 生态中该能力已被广泛使用仓库测试快照里可以看到实体生成器为外键生成的Index({ name: fk_users_cars_idx, invisible: true })用例见 tests/features/entity-generator/snapshots/FkIndexSelection.mysql.test.ts.snap。禁用索引Disabled Indexes禁用索引与隐形索引不同它不仅不被优化器使用写入时也不会被维护。其价值在于临时关闭索引以加速批量导入之后可通过ALTER INDEX ... REBUILD重新启用Entity() Index({ properties: [name], disabled: true, }) export class User { PrimaryKey() id!: number; Property() name!: string; }数据库支持范围仅 MSSQL。disabled选项同时存在于IndexOptions与UniqueOptions中见 packages/core/src/metadata/types.ts。聚集索引Clustered Indexes聚集索引决定表中数据的物理存储顺序每张表最多只能有一个聚集索引Entity() Index({ properties: [createdAt], clustered: true, }) export class Event { PrimaryKey() id!: number; Property() createdAt!: Date; }数据库支持范围MariaDB仅 Aria 存储引擎、MSSQL。:::note MariaDB 限制在 MariaDB 中聚集索引CLUSTERINGYES只对Aria 存储引擎生效。如果表使用 InnoDB该选项会被静默忽略而不产生任何效果。:::可延迟唯一约束PostgreSQL Deferrable ConstraintsPostgreSQL 支持“可延迟”约束约束检查可以推迟到事务提交时而非语句执行时进行这对批量更新中临时违反唯一性的场景非常有用Entity() Unique({ properties: [email], deferMode: INITIALLY_DEFERRED, // or INITIALLY_IMMEDIATE }) export class User { PrimaryKey() id!: number; Property() email!: string; }deferMode的两个取值语义INITIALLY_DEFERRED约束默认延迟到事务结束才检查INITIALLY_IMMEDIATE约束默认立即检查仍可在事务内通过SET CONSTRAINTS临时改为延迟。数据库支持范围仅 PostgreSQL。反向解析时PostgreSQL 方言会根据pg_indexes中的deferrable与initially_deferred标志还原出对应的deferMode见 PostgreSqlSchemaHelper.ts。JSON 列索引MikroORM 会自动处理 JSON 列的索引声明。当properties中的属性路径包含 JSON 字段时会生成对应的表达式索引expression index从而让查询能直接命中 JSON 内部的某个键Entity() Index({ properties: [metadata.email] }) // indexes a field within JSON export class User { PrimaryKey() id!: number; Property({ type: json }) metadata!: { email: string; phone: string }; }JSON 属性路径索引的实际落地方式取决于目标数据库对 JSON 的支持程度如 PostgreSQL 的-表达式、MySQL 的生成列方案等MikroORM 会在 Schema Generator 阶段将其转换为对应方言可执行的 DDL。组合多个高级选项上述高级选项可以自由组合例如同时声明命名、逐列排序、覆盖列与填充因子Entity() Index({ name: user_search_idx, properties: [lastName, firstName], columns: [ { name: lastName, sort: ASC }, { name: firstName, sort: ASC }, ], include: [email, phone], fillFactor: 80, }) export class User { PrimaryKey() id!: number; Property() firstName!: string; Property() lastName!: string; Property() email!: string; Property() phone!: string; }组合使用时需要注意一条来自源码的优先级规则如果同时指定了columns与propertiescolumns优先用于索引创建见 BaseOptions 注释columns里的name必须能对应到properties中声明的属性否则 Schema Generator 会报错。此外凡是携带columns、include、fillFactor、type、invisible、disabled、clustered等高级选项的索引都被视为“非平凡索引”必须通过实体级声明来表达——属性级Index()无法携带这些选项见 packages/sql/src/schema/DatabaseTable.ts。数据库支持总览下表汇总了各特性在不同数据库中的支持情况以 MikroORM 7 文档为准FeatureMySQLMariaDBPostgreSQLMSSQLSQLiteMongoDBBasic indexes✅✅✅✅✅✅Unique constraints✅✅✅✅✅✅Composite indexes✅✅✅✅✅✅Index expressions✅✅✅✅✅-Fulltext indexes✅✅✅✅-✅Sort order (ASC/DESC)✅✅✅✅✅-NULLS FIRST/LAST--✅---Column prefix length✅✅----Collation✅✅✅-✅-INCLUDE columns--✅✅--Fill factor--✅✅--Invisible indexes✅✅---✅Disabled indexes---✅--Clustered indexes-✅-✅--Deferrable constraints--✅---索引的生命周期Schema 生成、同步与迁移索引定义本身只是元数据真正落地依赖 MikroORM 的 Schema 管理能力Schema Generator负责把实体元数据中的indexes/uniques转换为CREATE INDEX/ALTER TABLE ... ADD CONSTRAINTDDL并支持schema:update自动同步差异与schema:diff输出变更 SQL。完整说明见 schema-generator.mdMigrations在生成迁移文件时索引与约束的增删改都会作为 schema 变更的一部分被记录生产环境通过迁移而不是schema:update来变更索引避免破坏性操作。见 migrations.mdSchema Comparator用于比对数据库现状与实体元数据的差异其中就包含 INCLUDE 列集合的比对逻辑见 packages/sql/src/schema/SchemaComparator.ts保证覆盖索引的新增/删除能被 schema diff 正确识别。编程式定义EntitySchema 与 defineEntity除了装饰器MikroORM 7 还支持通过EntitySchemaschema-first 风格或defineEntity()以纯对象形式声明索引与唯一约束。以defineEntity为例其DefineConfig中同样暴露了完整的indexes/uniques数组结构见 packages/core/src/entity/defineEntity.ts字段与装饰器选项一一对应indexes[].properties | name | type | options | expression | where | columns | include | fillFactor | invisible | disabled | clustereduniques[].properties | name | options | expression | where | deferMode | columns | include | fillFactor | disabled这意味着无论你使用哪种实体定义方式装饰器、EntitySchema、defineEntity高级索引能力都是完全一致的。相关入门可参考 defining-entities.md。实战建议先确认数据库能力再选特性对照文末的支持矩阵避免在 SQLite 上使用NULLS FIRST/LAST、在 MySQL 上使用INCLUDE列这类不支持的组合复合索引注意列顺序索引键列的顺序决定其能否命中查询的等值/排序条件columns选项可以精确控制每一列的 ASC/DESC大文本字段用前缀索引MySQL/MariaDB 上优先用length而非索引整个 TEXT 列可以显著减小索引体积并提升写入性能上线前用隐形索引做灰度验证MySQL 8 / MariaDB 10.6 上先用invisible: true观察查询计划与性能变化确认无误后再决定保留还是删除批量导入时利用禁用索引MSSQL 上可以临时disabled: true关闭索引加速写入导入完成后通过ALTER INDEX ... REBUILD重建保持索引声明与迁移一致生产环境通过 Migration 管理索引变更见 migrations.md并在 CI 中用schema:diff校验实体元数据与数据库实际状态没有漂移。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐Kronos K线预测模型三条命令跑出你的第一份K线预测Kronos K线预测模型三条命令跑出你的第一份K线预测 你会看K线但看多了之后总会卡在一个坎上看着像要涨可我怎么验证这个感觉手动回测几百根K线不后端MikroORM 7.1 索引与唯一约束完全指南从装饰器定义到跨方言 DDL 与查询时索引提示MikroORM 7.1 索引与唯一约束完全指南从装饰器定义到跨方言 DDL 与查询时索引提示 MikroORM 为实体提供了完整的索引Index与唯一约后端MikroORM 索引与唯一约束完整指南从 Index/Unique 装饰器到类型安全的 using 查询提示MikroORM 索引与唯一约束完整指南从 Index / Unique 装饰器到类型安全的 using 查询提示 MikroORM 在实体层提供了对索引后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案