Обновление оператора оптимизации (функции в предложении where) - PullRequest
3 голосов
/ 27 ноября 2009

Я чувствую себя тупым прямо сейчас.

Мне нужно обновить 100 000 строк в базе данных, к которой у меня нет прямого доступа. Общее количество строк таблицы составляет примерно 500 000 строк. Обновление просто добавляет один символ в поле, если его длина <3. Итак, в основном: </p>

UPDATE X SET VALUE = '0' || ЗНАЧЕНИЕ, ГДЕ ДЛИНА (ЗНАЧЕНИЕ) <3 </p>

Итак, я отправляю это обновление администраторам баз данных, и они возвращают его мне, говоря, что у оператора слишком высокие затраты производительности (потому что таблица полного доступа и фиксация 100 КБ) и что я должен написать процесс вместо этого. А потом они предоставляют мне пример кода, на случай, если я не знаю, как его создать.

Я говорю WTF, как процесс будет работать быстрее, чем один оператор обновления? После выполнения некоторых тестов, мое обновление занимает 30 секунд, процесс, следуя их примеру кода, занимает 10 минут.

Таким образом, настоящий вопрос, после всего этого разочарования, таков: есть ли способ избежать полной таблицы доступа при использовании такой функции в предложении where? (столбец проиндексирован)

Ответы [ 7 ]

9 голосов
/ 27 ноября 2009

Ваше утверждение уже оптимизировано. Он основан на множествах и запрашивает таблицу наиболее эффективным способом (полное сканирование таблицы). Вы не сможете написать программу, которая выполняет ту же работу с меньшими ресурсами / временем. Вы МОЖЕТЕ написать программу, которая работает плохо, которая не перезапускается в случае ошибки (т.е. фиксирует каждые 100 строк) и монополизирует больше ресурсов.

Следуйте Мантра Тома Кайта :

  • Вы должны сделать это в одном операторе SQL, если это вообще возможно.
  • Если вы не можете сделать это в одном операторе SQL, сделайте это в PL / SQL.
  • Если вы не можете сделать это в PL / SQL, попробуйте хранимую процедуру Java.
  • Если вы не можете сделать это в Java, сделайте это во внешней процедуре C.
  • Если вы не можете сделать это во внешней подпрограмме C, вы можете серьезно подумай, зачем тебе это нужно
4 голосов
/ 27 ноября 2009

Доступ к 100 тыс. Строк из 500 тыс. (То есть 20%) по индексу почти наверняка потребует больше логических операций ввода-вывода, чем полное сканирование. Я считаю, что ваше заявление в порядке.

2 голосов
/ 27 ноября 2009

На примечании стороны может быть более надежным:

ОБНОВЛЕНИЕ X SET VALUE = LPAD (VALUE, 3, '0') ГДЕ ДЛИНА (ЗНАЧЕНИЕ) <3 </p>

... на всякий случай.

1 голос
/ 27 ноября 2009

Единственная причина не делать это в одном операторе - это когда нужно обновить столько строк, чтобы ваши сегменты отката стали слишком маленькими.

В таком случае (только!), Жертвуя некоторой скоростью, вы можете сделать это следующим образом в PL / SQL:

DECLARE mylimit 10000; /* Arbitrary limit */
BEGIN
  LOOP
    UPDATE X SET VALUE = '0'||VALUE WHERE LENGTH(VALUE) < 3 and ROWNUM<=mylimit;
    EXIT WHEN SQL%ROWCOUNT<mylimit;
  END LOOP;
END;

Но это также не работает идеально, потому что строки, длина которых (VALUE) = 1, будут обновляться дважды, пока они больше не будут удовлетворять условию WHERE. К сожалению, этого нельзя легко избежать ...

0 голосов
/ 01 декабря 2009

Индекс на основе функций может помочь ускорить увеличение.

create index x_idx1 on x(length(value));

Вот пример.

sqlplus>create table t
  2  ( id NUMBER(9) PRIMARY KEY,
  3  name VARCHAR2(100)
  4  );
Table created.

sqlplus>insert into t select object_id, object_name from user_objects;
2188 rows created.

sqlplus>exec  dbms_stats.gather_table_stats(ownname=>'test',tabname =>'t');
PL/SQL procedure successfully completed.

sqlplus>create index t_idx3 on t(length(name));
Index created.

sqlplus>explain plan for update t set name = name || '1' where length(name) < 25;
Explained.

sqlplus>select * from table(dbms_xplan.display);
--------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT             |        |   109 |  2616 |     4   (0)| 00:00:01 |
|   1 |  UPDATE                      | T      |       |       |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| T      |   109 |  2616 |     4   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | T_IDX3 |    20 |       |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access(LENGTH("NAME")<25)
15 rows selected.
0 голосов
/ 29 ноября 2009

Если обновление всей таблицы в одной транзакции невозможно (например, из-за проблем с пространством отката), другой метод состоит в разбиении таблицы на куски (например, в диапазонах значений PK) и обновлении их по одному фрагменту за раз .

Попробуйте выбрать метод "чанкирования", при котором все строки будут в одном и том же блоке, например если строки обычно вставляются примерно в порядке ID, это было бы хорошим кандидатом - таким образом, обновление будет обновлять все строки в блоке за один раз.

Если ваш предикат покрывает большую часть строк в таблице, я ожидаю полного сканирования таблицы для каждого обновления, что должно быть в порядке. (Вы даже можете отслеживать их прогресс, запрашивая v $ session_longops.)

0 голосов
/ 27 ноября 2009

Похоже, единственный вариант, который у вас есть, - это выполнить ОБНОВЛЕНИЕ в виде фрагментов. Если вы, например, добавили LIMIT 1000 в свой оператор, производительность не должна заметно снизиться (я предполагаю, что этот запрос должен выполняться в действующей базе данных).

Вы говорите, что у вас нет прямого доступа; если эти люди могут запускать сценарии Bash, вы можете просто зациклить оператор с LIMIT столько раз, сколько необходимо, поместив sleep # в цикл. Может быть, это был бы жизнеспособный обходной путь.


Как отмечали другие - да, однократное ОБНОВЛЕНИЕ уже является самым быстрым методом . Но, похоже, его проблема в том, что даже это занимает слишком много времени, поэтому я предложил сделать этот фрагмент по частям.

Выполнение по частям будет выполняться еще дольше, пока оно не будет выполнено, но оно не должно занимать базу данных и сделать ее недоступной для других пользователей (если вы выберете хороший интервал, то есть). Проблема заключается в записи в базу данных, а не в поиске (то есть с использованием WHERE LENGTH(name) < 3). Таким образом, хотя этот метод увеличит общую нагрузку на базу данных, он будет распространяться со временем и, следовательно, не будет блокировать базу данных. Вы могли бы, например, запустите это на 100 кусках и сделайте паузу через две секунды после каждого. Пусть это пройдет всю ночь, и никто не заметит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...