JDBC Oracle - выбор схемы объяснения запроса
Мне интересно, как я могу получить план объяснения с помощью Java. Причина, в которой я нуждаюсь, заключается в том, что у нас есть структура, где специальные пользователи могут создавать отчеты. Эти отчеты иногда создают огромные запросы, в которых мы хотим объяснить "на лету" и сохранить стоимость. Таким образом, мы можем позже проанализировать высокозатратные запросы и оптимизировать их.
Пример кода, который дает мне незаконное исключение столбца:
ResultSet rs = null;
try {
oracle = ConnectionManager.getConnection(ConnectionManager.Test);
pstmt = oracle.prepareStatement("begin execute immediate
'explain plan for SELECT 1 from Dual'; end;");
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(1));
}
Ответы
Ответ 1
Используйте это:
oracle = ConnectionManager.getConnection(ConnectionManager.Test);
stmt = oracle.createStatement()
stmt.execute("explain plan for SELECT 1 from Dual");
rs = stmt.executeQuery("select plan_table_output from table(dbms_xplan.display())");
while (rs.next())
{
System.out.println(rs.getString(1));
}
Ответ 2
Существует также способ показать реальный план, используемый для запуска последнего запроса в этом сеансе через DBMS_XPLAN.DISPLAY_CURSOR
. Интересующий запрос не должен быть добавлен с помощью EXPLAIN PLAN FOR
.
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(
"select plan_table_output from table(dbms_xplan.display_cursor())")) {
while (rs.next()) {
System.out.println(rs.getString(1));
}
}
}
Обратите внимание, что пользователю необходимо предоставить следующие разрешения для использования DBMS_XPLAN.DISPLAY_CURSOR
:
GRANT SELECT ON v_$session TO USER;
GRANT SELECT ON v_$sql_plan TO USER;
GRANT SELECT ON v_$sql_plan_statistics_all TO USER;
GRANT SELECT ON v_$sql TO USER;
Кредиты переходят на https://myoracledbablog.wordpress.com/2016/07/26/dbms_xplan-and-the-user-has-no-select-privilege-on-v-error/.
См. Также https://blogs.oracle.com/optimizer/how-do-i-display-and-read-the-execution-plans-for-a-sql-statement.
Но я столкнулся с тем, что вызов dbms_xplan.display_cursor()
сразу после выполненного запроса может по-прежнему возвращать несвязанные результаты a, если многопотоковое приложение использует общий пул соединений.
Это можно обойти, sql_id
поиск самого последнего sql_id
в системном представлении v$sql
и предоставив его как параметр dbms_xplan.display_cursor
.
Итак, вот готовый к использованию java-код для регистрации фактического плана выполнения недавно выполненного запроса по его sql (возможно, частичный).
public void explainActualPlan(String sql, boolean sqlIsPartial, Logger log) {
if (!log.isTraceEnabled()) return;
try (Connection connection = dataSource.getConnection()) {
String sqlId;
String sqlFilter = sqlIsPartial
? "sql_text like '%' || ? || '%'"
//+ " and parsing_schema_id = sys_context('USERENV', 'CURRENT_SCHEMAID')"
: (sql.length() <= 1000 ? "sql_text = ?" : "dbms_lob.compare(sql_fulltext, ?) = 0");
try (PreparedStatement st = connection.prepareStatement(
"select sql_id from v$sql where " + sqlFilter +
" order by last_active_time desc fetch next 1 row only")) {
st.setString(1, sql);
try (ResultSet rs = st.executeQuery()) {
if (rs.next()) {
sqlId = rs.getString(1);
} else {
log.warn("Can't find sql_id for sql '{}'. Has it really been just executed?", sql);
return;
}
}
}
String planFormat = "TYPICAL";
if (sql.contains("GATHER_PLAN_STATISTICS")) {
planFormat += " ALLSTATS LAST +cost +bytes OUTLINE";
}
try (PreparedStatement st = connection.prepareStatement(
"select plan_table_output from table(dbms_xplan.display_cursor(" +
"sql_id => ?, format => '" + planFormat + "'))")) {
st.setString(1, sqlId);
try (ResultSet rs = st.executeQuery()) {
StringBuilder sb = new StringBuilder("Last query plan:\n");
while (rs.next()) {
sb.append(rs.getString(1)).append('\n');
}
log.trace(sb.toString());
}
}
} catch (Exception e) {
log.warn("Failed to explain query plan for '{}'", sql, e);
log.warn("Check that permissions are granted to the current db user:\n"
+ "GRANT SELECT ON v_$session TO <USER>;\n"
+ "GRANT SELECT ON v_$sql_plan TO <USER>;\n"
+ "GRANT SELECT ON v_$sql_plan_statistics_all TO <USER>;\n"
+ "GRANT SELECT ON v_$sql TO <USER>;\n"
);
}
}
Некоторые примечания:
- Oracle всегда преобразует подготовленные параметры оператора из
?
to :n
перед сохранением текста запроса в v$sql
, поэтому поиск по sql с помощью ?
не найдут совпадений - оба
v$sql.sql_text
(усекаются до первых 1000 символов) и v$sql.sql_fulltext
(полный CLOB) сохраняют sql-текст без разрывов строк, поэтому может потребоваться выполнить соединение с V$SQLTEXT_WITH_NEWLINES
если вы используете их в запросе текст -
LIKE
совместимость используется в частичном режиме, поэтому может понадобиться избежать специальных символов "%" и "_" - Я проверил, что Oracle позволяет включать любые неизвестные строки в комментарии к комментариям, например
/*+ labuda FIRST_ROWS(200) */
. Он по-прежнему будет применять известные подсказки, если приложение является действительным идентификатором (буквенно-цифровым и начинается с буквы). Это может быть полезно для отслеживания интересующих запросов путем добавления некоторого хеш-кода в предложение hints. -
[email protected]
можно было дополнительно отфильтровать and parsing_schema_id = sys_context('USERENV', 'CURRENT_SCHEMAID')
но это исключало бы некоторые планы, если экземпляр DB будет использоваться несколькими аналогичными приложениями в разных схемах с точно соответствующими запросами sql - приведенный выше код предоставляет дополнительные сведения в выводе плана в случае, если sql был выполнен с подсказкой
GATHER_PLAN_STATISTICS
Вот пример выведенного выше кода для запроса из моего другого ответа:
22:54:53.558 TRACE o.f.adminkit.AdminKitSelectorQuery - Last query plan:
SQL_ID c67mmq4wg49sx, child number 0
-------------------------------------
select * from (select * from (select /*+ FIRST_ROWS(200)
INDEX_RS_DESC("FR_MESSAGE_PART" ("TS")) GATHER_PLAN_STATISTICS */ "ID",
"MESSAGE_TYPE_ID", "TS", "REMOTE_ADDRESS", "TRX_ID",
"PROTOCOL_MESSAGE_ID", "MESSAGE_DATA_ID", "TEXT_OFFSET", "TEXT_SIZE",
"BODY_OFFSET", "BODY_SIZE", "INCOMING" from "FR_MESSAGE_PART" where
"TS" + 0 >= :1 and "TS" < :2 and "ID" >= 376894993815568384 and "ID" <
411234940974268416 order by "TS" DESC) where ROWNUM <= 200) offset 180
rows
Plan hash value: 2499404919
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time | Pstart| Pstop | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | | 640K(100)| | | | 20 |00:00:00.01 | 322 | | | |
|* 1 | VIEW | | 1 | 200 | 130K| | 640K (1)| 00:00:26 | | | 20 |00:00:00.01 | 322 | | | |
| 2 | WINDOW NOSORT | | 1 | 200 | 127K| | 640K (1)| 00:00:26 | | | 200 |00:00:00.01 | 322 | 142K| 142K| |
| 3 | VIEW | | 1 | 200 | 127K| | 640K (1)| 00:00:26 | | | 200 |00:00:00.01 | 322 | | | |
|* 4 | COUNT STOPKEY | | 1 | | | | | | | | 200 |00:00:00.01 | 322 | | | |
| 5 | VIEW | | 1 | 780K| 487M| | 640K (1)| 00:00:26 | | | 200 |00:00:00.01 | 322 | | | |
|* 6 | SORT ORDER BY STOPKEY | | 1 | 780K| 68M| 89M| 640K (1)| 00:00:26 | | | 200 |00:00:00.01 | 322 | 29696 | 29696 |26624 (0)|
| 7 | PARTITION RANGE ITERATOR | | 1 | 780K| 68M| | 624K (1)| 00:00:25 | 3 | 2 | 400 |00:00:00.01 | 322 | | | |
|* 8 | COUNT STOPKEY | | 2 | | | | | | | | 400 |00:00:00.01 | 322 | | | |
|* 9 | TABLE ACCESS BY LOCAL INDEX ROWID| FR_MESSAGE_PART | 2 | 780K| 68M| | 624K (1)| 00:00:25 | 3 | 2 | 400 |00:00:00.01 | 322 | | | |
|* 10 | INDEX RANGE SCAN DESCENDING | IX_FR_MESSAGE_PART_TS | 2 | 559K| | | 44368 (1)| 00:00:02 | 3 | 2 | 400 |00:00:00.01 | 8 | | | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
OPT_PARAM('optimizer_dynamic_sampling' 0)
OPT_PARAM('_optimizer_dsdir_usage_control' 0)
FIRST_ROWS(200)
OUTLINE_LEAF(@"SEL$3")
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
OUTLINE_LEAF(@"SEL$4")
NO_ACCESS(@"SEL$4" "from$_subquery$_004"@"SEL$4")
NO_ACCESS(@"SEL$1" "from$_subquery$_001"@"SEL$1")
NO_ACCESS(@"SEL$2" "from$_subquery$_002"@"SEL$2")
INDEX_RS_DESC(@"SEL$3" "FR_MESSAGE_PART"@"SEL$3" ("FR_MESSAGE_PART"."TS"))
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("from$_subquery$_004"."rowlimit_$$_rownumber">180)
4 - filter(ROWNUM<=200)
6 - filter(ROWNUM<=200)
8 - filter(ROWNUM<=200)
9 - filter("ID">=376894993815568384)
10 - access("TS"<:2)
filter((INTERNAL_FUNCTION("TS")+0>=:1 AND "TS"<:2))