索引

索引を適切に使用することは、優れたパフォーマンスを実現するために不可欠です。MongoDBのためのOracle Database APIを実行する場合は、MongoDB createIndexesコマンドを発行して索引を作成します。ほとんどのMongoDB索引タイプは、機能サポートにリストされている制限に従ってサポートされています。

索引を作成すると、MongoDBのためのOracle Database APIによって対応するSQL create json index文が生成されます。次に例を示します:

db.employees.insertMany([
    {"_id": 1, "name" : "SMITH", "job" : "CLERK", "sal" : 800},
    {"_id": 2, "name" : "ALLEN", "job" : "SALESMAN", "sal" : 1600},
    {"_id": 3, "name" : "ALLEN", "job" : "CLERK", "sal" : 1250},
    {"_id": 4, "name" : "WARD", "job" : "CLERK", "sal" : 1300}
]);

db.employees.createIndex({"job":1}, {"name":"job_index"});

この例では、4つのドキュメントを含むemployeesコレクションを作成し、jobフィールドに索引を追加します。SQLから同じ索引を作成するには、create json index文を使用します:

db.aggregate([{$sql:`
    create json index job_index on employees e (f.data.job)
`}]);

findaggregateなどのMongoDBコマンドおよびSQL問合せでは、これらの索引を使用できます。explainコマンドを使用して、特定のコマンドが索引を使用するかどうかを確認します。例を続けます:

db.employees.find({job:"SALESMAN"});
[ { _id: 2, name: 'ALLEN', job: 'SALESMAN', sal: 1600 } ]

scott> db.employees.find({job:"SALESMAN"}).explain();
{
  queryPlanner: {
    namespace: 'scott.employees',
    parsedQuery: { job: 'SALESMAN' },
    rewrittenQuery: { job: { '$stringOnly': 'SALESMAN' } },
    generatedSql: `select "DATA",rawtohex("RESID"),"ETAG" from "SCOTT"."employees" where JSON_EXISTS("DATA",'$?(@.job.stringOnly() == $B0)' passing ? as "B0" type(strict))`,
    winningPlan: ' Plan Hash Value  : 3517912025 \n' +
      '\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '| Id  | Operation                             | Name                   | Rows | Bytes | Cost | Time     |\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '|   0 | SELECT STATEMENT                      |                        |    1 | 24501 |    2 | 00:00:01 |\n' +
      '|   1 |   TABLE ACCESS BY INDEX ROWID BATCHED | employees                  |    1 | 24501 |    2 | 00:00:01 |\n' +
      '| * 2 |    INDEX RANGE SCAN (MULTI VALUE)     | $ora:employees.job_index |    1 |       |    1 | 00:00:01 |\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '\n' +
      'Predicate Information (identified by operation id):\n' +
      '------------------------------------------\n' +
      `* 2 - access(JSON_QUERY("DATA" /*+ LOB_BY_VALUE */ FORMAT OSON , '$."job"[*]' RETURNING ANY ORA_RAWCOMPARE ASIS WITHOUT ARRAY WRAPPER ERROR ON ERROR PRESENT ON EMPTY NULL ON MISMATCH TYPE(LAX)\n` +
      '  MULTIVALUE)=SYS_CONS_ANY_SCALAR(:1, 4))\n' +
      '\n' +
      '\n' +
      'Notes\n' +
      '-----\n' +
      '- Dynamic sampling used for this statement ( level = 2 )\n' +
      '\n'
  },
  serverInfo: { host: 'localhost', port: 27017, version: '7.0.22' },
  ok: 1
}

findコマンドは、jobフィールドがSALESMANと等しいドキュメントを取得します。explainコマンドは、生成されたSQLおよび問合せ計画を表示し、計画でjob索引($ora:employees.job_index)が使用されていることを確認します。

MongoDBコマンドを使用して作成された索引は、SQL問合せでも使用できます。例を続けます:

db.aggregate([{ $sql: `
    select data
    from employees e
    where e.data.job = 'SALESMAN'
` }]);
[ { _id: 2, name: 'ALLEN', job: 'SALESMAN', sal: 1600 } ]

db.aggregate([{ $sql: `
    select data
    from employees e
    where e.data.job = 'SALESMAN'
` }]).explain();
{
  queryPlanner: {
    namespace: 'scott.1',
    parsedQuery: {},
    generatedSql: "\n    select data\n    from employees e\n    where e.data.job = 'SALESMAN'\n",
    winningPlan: ' Plan Hash Value  : 3517912025 \n' +
      '\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '| Id  | Operation                             | Name                   | Rows | Bytes | Cost | Time     |\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '|   0 | SELECT STATEMENT                      |                        |    1 | 22489 |    1 | 00:00:01 |\n' +
      '| * 1 |   TABLE ACCESS BY INDEX ROWID BATCHED | employees                  |    1 | 22489 |    1 | 00:00:01 |\n' +
      '| * 2 |    INDEX RANGE SCAN (MULTI VALUE)     | $ora:employees.job_index |    1 |       |    1 | 00:00:01 |\n' +
      '---------------------------------------------------------------------------------------------------------\n' +
      '\n' +
      'Predicate Information (identified by operation id):\n' +
      '------------------------------------------\n' +
      `* 1 - filter(JSON_VALUE("F"."DATA" /*+ LOB_BY_VALUE */ FORMAT OSON , '$.job' RETURNING VARCHAR2(4000) NULL ON ERROR TYPE(STRICT) )='SALESMAN')\n` +
      `* 2 - access(JSON_QUERY("DATA" /*+ LOB_BY_VALUE */ FORMAT OSON , '$."job"[*]' RETURNING ANY ORA_RAWCOMPARE ASIS WITHOUT ARRAY WRAPPER ERROR ON ERROR PRESENT ON EMPTY NULL ON MISMATCH TYPE(LAX)\n` +
      "  MULTIVALUE)=SYS_CONS_ANY_SCALAR('SALESMAN', 4))\n" +
      '\n' +
      '\n' +
      'Notes\n' +
      '-----\n' +
      '- Dynamic sampling used for this statement ( level = 2 )\n' +
      '\n' +
      '\n'
  },
  ok: 1
}

$sqlステージは、job属性がSALESMANと等しいドキュメントを返すSQL問合せを評価します。実行計画は、SQLがMongoDB createIndexesコマンドによって作成されたjob索引も使用することを示しています。MongoDBコマンドとSQL問合せは、同じ索引を共有します。

索引のヒント

MongoDBヒントを使用して、findおよびaggregationコマンドの索引選択に影響を与えます。索引名または索引仕様ドキュメントを指定します。次に例を示します:

db.employees.insertMany([
    {"_id": 1, "name" : "SMITH", "job" : "CLERK", "sal" : 800},
    {"_id": 2, "name" : "ALLEN", "job" : "SALESMAN", "sal" : 1600},
    {"_id": 3, "name" : "ALLEN", "job" : "CLERK", "sal" : 1250},
    {"_id": 4, "name" : "WARD", "job" : "CLERK", "sal" : 1300}
]);

db.employees.createIndex({"job":1});

db.employees.createIndex({"name":1});

db.employees.find({"job":"CLERK", "name":"ALLEN"});
[ { _id: 3, name: 'ALLEN', job: 'CLERK', sal: 1250 } ]

db.employees.find({"job":"CLERK", "name":"ALLEN"}).explain();
{
  queryPlanner: {
    winningPlan: 
      ...
      '| * 2 |    INDEX RANGE SCAN (MULTI VALUE)     | $ora:employees.job_1    ' 
      ...
  }
}

db.employees.find({"job":"CLERK", "name":"ALLEN"}).hint("name_1").explain();
{
  queryPlanner: {
    winningPlan: 
      ...
      '| * 2 |    INDEX RANGE SCAN (MULTI VALUE)     | $ora:employees.name_1     ' 
      ...
  }
}

db.employees.find({"job":"CLERK", "name":"ALLEN"}).hint({"name":1}).explain();
{
  queryPlanner: {
    winningPlan:
      ...
      '| * 2 |    INDEX RANGE SCAN (MULTI VALUE)     | $ora:employees.name_1     ' 
      ...
  }
}

この例では、4つのドキュメントを含むemployeesコレクションを作成し、jobおよびnameフィールドに索引を定義します。両方のフィールドをフィルタする問合せでは、デフォルトでjob索引が使用されます。この例では、最初に索引名"name_1"を持つヒントを適用してから、索引仕様ドキュメント{ "name": 1 }を使用して同じヒントを再度適用します。

SQLから索引を作成するか、createIndexesの使用時にカスタム名を割り当てる場合は、その正確な名前をヒントに指定します(たとえば、$ora:employees.job_index)。{ name: 1 }などの索引仕様ドキュメントは、デフォルト名を保持するMongoDB索引(name_1など)にのみ適用されます。

索引のネーミング

索引名の一意性ルールは、Oracle DatabaseとMongoDBで異なります。MongoDBでは、索引名はコレクション内で一意であるため、同じデータベース内の異なるコレクションで索引名を共有できます。Oracle Databaseでは、索引名はスキーマ内で一意である必要があるため、同じスキーマ内のコレクションで同じ索引名を再利用することはできません。競合を回避するために、MongoDBのためのOracle Database APIでは、生成された索引名に$ora:<collection name>を使用して接頭辞を付けます。listIndexesを実行すると、レスポンスにはローカル名と接頭辞付きの完全修飾名の両方が含まれます。例を続けます:

db.employees.getIndexes();
[
  {
    v: 2,
    fullName: '$ora:employees.job_1',
    name: 'job_1',
    key: { job: 1 }
  },
  {
    v: 2,
    fullName: '$ora:employees.name_1',
    name: 'name_1',
    key: { name: 1 }
  },
  { v: 2, key: { _id: 1 }, name: '_id_' }
]

employeesコレクションのlistIndexes出力には、生成された接頭辞を含むfullNameフィールドと、接頭辞を含まないnameフィールドが表示されます。