Оптимизация запросов СУБД

Поделись знанием:
Перейти к: навигация, поиск

Оптимизация запросов — это 1) функция СУБД, осуществляющая поиск оптимального плана выполнения запросов из всех возможных для заданного запроса, 2) процесс изменения запроса и/или структуры БД с целью уменьшения использования вычислительных ресурсов при выполнении запроса. Один и тот же результат может быть получен СУБД различными способами (планами выполнения запросов), которые могут существенно отличаться как по затратам ресурсов, так и по времени выполнения. Задача оптимизации заключается в нахождении оптимального способа.

В реляционной СУБД оптимальный план выполнения запроса — это такая последовательность применения операторов реляционной алгебры к исходным и промежуточным отношениям, которая для конкретного текущего состояния БД (её структуры и наполнения) может быть выполнена с минимальным использованием вычислительных ресурсов.

В настоящее время известны две стратегии поиска оптимального плана:

  • грубой силы путём оценки всех перестановок соединяемых таблиц, используемых способов входа в таблицы и типов соединения (то есть полный перебор вариантов);
  • на основе генетического алгоритма путём оценки ограниченного числа перестановок.

Также некоторые СУБД позволяют программисту вмешиваться в поиск оптимального плана в различной степени, от минимального влияния до полного и чёткого указания какой именно план запроса использовать.

Планы выполнения запроса сравниваются исходя из множества факторов (реализации в различных СУБД отличаются), в том числе:

  • потенциальное число строк, извлекаемое из каждой таблицы, получаемое из статистики;
  • наличие индексов;
  • возможность выполнения слияний (merge-join);
  • способ чтения записей/блоков таблиц/индексов.

В общем случае соединение выполняется вложенными циклами. Однако этот алгоритм может оказаться менее эффективен, чем специализированные алгоритмы. Например, если у сливаемых таблиц есть индексы по соединяемым полям, или одна или обе таблицы достаточно малы, чтобы быть отсортированными в памяти, то исследуется возможность выполнения слияний.





Стратегии оптимизации

Как уже отмечалось, суть оптимизации заключается в поиске минимума функции стоимости от перестановки таблиц. Независимо от стратегии, оптимизатор обязан уметь анализировать стоимость для произвольной перестановки, в то время как сами перестановки для анализа предоставляются другим алгоритмом. Исследуемое множество перестановок может отличаться от всего пространства перестановок. Исходя из этого, обобщённый алгоритм работы оптимизатора можно записать так:

Перебор всех планов в поисках наилучшего

  ТекущийПорядокТаблиц := НайтиИсходныйПорядокТаблиц;
  ЛучшийПорядокТаблиц := ТекущийПорядокТаблиц;
  НаименьшаяСтоимость := МаксимальноВозможнаяСтоимость;

  Выполнять
    Стоимость := ОценитьСтоимость(ТекущийПорядокТаблиц);

    Если Стоимость < НаименьшаяСтоимость То
      ЛучшийПорядокТаблиц := ТекущийПорядокТаблиц;
      НаименьшаяСтоимость := Стоимость;
    КонецЕсли;

    ТекущийПорядокТаблиц := НайтиСледующийПорядокТаблиц;
  Пока (ДоступенСледующийПорядокТаблиц);

Стратегия грубой силы

В теории, при использовании стратегии грубой силы оптимизатор запросов исследует все пространство перестановок всех исходных выбираемых таблиц и сравнивает суммарные оценки стоимости выполнения соединения для каждой перестановки. На практике, при разработке System R было предложено ограничить пространство исследования только левосторонними соединениями, чтобы при выполнении запроса одна из таблиц всегда была представлена образом на диске. Исследование нелевосторонних соединений имеет смысл если таблицы, входящие в соединения, расположены на более чем одном узле.

Для каждой таблицы в каждой из перестановок по статистике оценивается возможность использования индексов. Перестановка с минимальной оценкой и есть итоговый план выполнения запроса.

Алгоритмы генерации всех перестановок обсуждаются в четвёртом томе секции 2 «Искусства программирования для ЭВМ» Дональда Кнута (см. список литературы).

Оценка стоимости плана на основе текущей перестановки таблиц

/*
 * Making estimation of query cost accordingly
 * current order of tables in query
 * Function returns value < 0  if it decides not to
 * make all steps of estimation because the cost of
 * this order >> the_best_cost (if the_best_cost > 0)
 * In another case it returns estimated cost (>=0)
 */
static float
est_cost_order (i4_t *res_row_num)
{
  MASK Depend = _AllTblMsk;
  i4_t tbl_num, prdc_num, i, real_num, ColNum;
  float cost_all = 0.0, row_num = 1.0;
  float ind_best_sel, sel;
  SP_Ind_Info *cur_ind_info;

  /* estimation of the cost of tables scanning */
  for (tbl_num = 0; tbl_num < number_of_tables; tbl_num++)
    {
      ind_best_sel = 1.0;
      real_num = cur_tbl_order [tbl_num];
      TblAllBits[tbl_num] = Depend = BitOR (Depend, TblBits [real_num]);

      /* init of array for information about culumns */
      for (i = 0; i < tab_col_num[real_num]; i++)
   col_stat[i].Sel = 1.0;

      /* checking information about SPs for current table */
      for (prdc_num = 0; prdc_num < number_of_SPs; prdc_num++)
   if (!(SPs[prdc_num].flag) /* this predicate wasn't used yet */ &&
       CAN_BE_USED (SPs[prdc_num].Depend, Depend)
       /* predicate can be used now */)
     {
       SPs[prdc_num].flag++;
       cur_ind_info = (SPs_indexes[real_num]) + prdc_num;
       if (cur_ind_info->Sel)
         { /* this predicate is SP for current table */
      ColNum = cur_ind_info->ColNum;
      if (col_stat[ColNum].Sel > cur_ind_info->Sel)
        {
          col_stat[ColNum].Sel = cur_ind_info->Sel;
          if (cur_ind_info->IndExists /* there is index for the column of this SP */
         && col_stat[ColNum].Sel < ind_best_sel)
            ind_best_sel = col_stat[ColNum].Sel;
        }
         }
     }

     /* finding of common selectivity of all simple predicates for current table */
      for (i = 0, sel = 1.0; i < tab_col_num[real_num]; i++)
   sel *=col_stat[i].Sel;

      /* adding of default selectivity for the rest of predicates */
      for (prdc_num = number_of_SPs; prdc_num < number_of_disjuncts; prdc_num++)
   if (!(SPs[prdc_num].flag) /* this predicate wasn't used yet */ &&
       CAN_BE_USED (SPs[prdc_num].Depend, Depend)/* predicate can be used now */
      )
     {
       SPs[prdc_num].flag++;
            sel *= DEFAULT_SEL;
          }

      number_of_scanned_rows [tbl_num] = number_of_rows[real_num] * ind_best_sel * row_num;
      /* number_of_scanned_rows [i] - estimated number of rows read from i-th table */
      cost_all += number_of_scanned_rows [tbl_num] + OPEN_SCAN_COST * row_num;
      row_num *= number_of_rows[real_num] * sel;

    } /* for tbl_num: tables handling */
  for (prdc_num = 0; prdc_num < number_of_disjuncts; prdc_num++)
    SPs[prdc_num].flag = 0;

  /* adding of the cost of all subqueries */
  for (prdc_num = 0; prdc_num < number_of_disjuncts; prdc_num++)
    {
      for (tbl_num = 0; tbl_num < number_of_tables; tbl_num++)
        if (CAN_BE_USED (SPs[prdc_num].SQ_deps, TblAllBits[tbl_num]))
          break;
      assert (tbl_num < number_of_tables);

      /* tbl_num - number of the last (in order) table *
       * that is referenced in the predicate           */
      cost_all += SPs[prdc_num].SQ_cost * number_of_scanned_rows [tbl_num];
    }

  *res_row_num = (row_num < 1.0) ? 1 :
    ((row_num < FLT_MAX_LNG) ? (i4_t)row_num : MAX_LNG);

  return cost_all;
} /* est_cost_order */

Здесь cur_tbl_order — знакомый по предыдущему примеру вектор, содержащий текущий порядок таблиц.

Стратегия на основе генетического алгоритма

С ростом числа таблиц в запросе количество возможных перестановок растет как n!, следовательно, пропорционально растет и время оценки для каждой из них. Это делает проблематичным оптимизацию запросов на основе большого числа таблиц. В поисках решения этой проблемы в 1991 году Kristin Bennett, Michael Ferris, Yannis Ioannidis предложили использовать генетический алгоритм для оптимизации запросов, который дает субоптимальное решение за линейное время.

При использовании генетического алгоритма исследуется только часть пространства перестановок. Таблицы, участвующие в запросе, кодируются в хромосомы. Над ними выполняются мутации и скрещивания. На каждой итерации выполняется восстановление хромосом для получения осмысленной перестановки таблиц и отбор хромосом, которые дают минимальные оценки стоимости. В результате отбора остаются только те хромосомы, которые дают меньшее, по сравнению с предыдущей итерацией, значение функции стоимости. Таким образом происходит исследование и нахождение локальных минимумов функции стоимости. Предполагается, что глобальный минимум не дает существенных преимуществ, по сравнению с лучшим локальным минимумом. Алгоритм повторяется несколько итераций, после чего выбирается наиболее эффективное решение.

Оценка альтернативных способов выполнения

При оценке планов выполнения запросов исследуются альтернативные способы выполнения реляционных соединений. Способы выполнения соединений отличаются по эффективности, но обладают ограничениями по применению.

Вложенные циклы

В случае вложенных циклов внешний цикл извлекает все строки из внешней таблицы и для каждой найденной строки вызывает внутренний цикл. Внутренний цикл по условиям объединения и данным внешнего цикла ищет строки во внутренней таблице. Циклы могут вкладываться произвольное количество раз. В этом случае внутренний цикл становится внешним для следующего цикла и т. д.

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

Выбор индекса

Для выбора индекса для каждой таблицы прежде всего находятся все потенциальные индексы, которые могут быть применены в исследуемом запросе. Поскольку ключи в индексе упорядочены, то эффективная выборка из него может выполняться только в лексикографическом порядке. В связи с этим, выбор индекса основывается на наличии ограничений для колонок, входящих в индекс, начиная с первой.

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

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

Оставшиеся индексы ранжируются по числу извлекаемых строк и длине ключа. Очевидно, число извлекаемых строк зависит от ограничений. Чем меньше число извлекаемых строк и короче длина ключа, тем выше ранг.

Индекс с наивысшим рангом используется для выборки.

Сканирование индекса целиком

Для выполнения некоторых запросов с агрегацией индекс может сканироваться целиком. В частности:

  • Для поиска глобальных максимальных и минимальных значений использоваться индекс по соответствующей колонке (колонкам) без ограничений;
  • Для поиска числа строк в таблице используется индекс по первичному ключу, если таковой имеется. Это связано с тем, что СУБД не хранит и не может хранить число строк в таблице, а сканирование индекса по первичному ключу наименее ресурсоемко.

Если запрошенный порядок выборки совпадает с порядком одного или более индексов, то выполняется оценка возможности сканирования одного из таких индексов целиком.

Если индекс содержит все колонки, необходимые для получения результата, то сканирование таблицы не происходит. Если оптимизатор использует этот фактор, то можно ускорить выборку из таблицы для специализированных запросов путём включения в индекс избыточных колонок, которые будут извлечены непосредственно при поиске по ключу. Подобный метод ускорения запросов следует использовать осторожно.

Слияние

Если объединяемые таблицы имеют индексы по сравниваемым полям, или одна или обе таблицы достаточно малы, чтобы быть отсортированными в памяти, то объединение может быть выполнено с помощью слияния. Оба отсортированных набора данных сканируются и в них ищутся одинаковые значения. За счёт сортировки слияние эффективнее вложенных циклов на больших объёмах данных, но план выполнения не может начинаться со слияния.

Оценка числа извлекаемых строк

Оценка числа извлекаемых из таблицы строк используется для принятия решения о полном сканировании таблицы вместо доступа по индексу. Решение принимается на том основании, что каждое чтение листовой страницы индекса с диска влечет за собой 1 или более позиционирований и 1 или более чтений страниц таблицы. Поскольку индекс содержит ещё и нелистовые страницы, то извлечение более 0.1-1 % строк из таблицы, как правило, эффективней выполнять полным сканированием таблицы.

Более точная оценка получится на основе следующих показателей:

  1. Число извлекаемых строк
  2. Средняя длина ключа в индексе
  3. Среднее число строк в странице индекса
  4. Длина страницы индекса
  5. Высота B*-дерева в индексе
  6. Средняя длина строки в таблице
  7. Среднее число строк в странице таблицы
  8. Длина страницы таблицы

СУБД старается организовать хранение блоков данных одной таблицы последовательно с целью исключить накладные расходы на позиционирование при полном сканировании (СУБД Oracle использует предварительное выделение дискового пространства для файлов данных). Эффективность полного сканирования так же увеличивается за счёт упреждающего чтения. При упреждающем чтении СУБД одновременно выдает внешней памяти команды чтения нескольких блоков. Сканирование начинается по завершении чтения любого из блоков. Одновременно продолжается чтение остальных блоков. Эффективность достигается за счёт параллелизма чтения и сканирования.

Оптимизация параллельных сортировок

Если СУБД запущена на нескольких процессорах, то для уменьшения времени ответа сортировки могут выполняться параллельно. Необходимым условием для этого является возможность поместить все извлекаемые данные в оперативную память. Для выполнения сортировки извлекаемые данные разделяются на фрагменты, число которых равно числу процессоров. Каждый из процессоров выполняет сортировку над одним из фрагментов независимо от других. На финальном шаге выполняется слияние отсортированных фрагментов, либо слияние совмещается с выдачей данных клиенту СУБД.

Если СУБД запущена на нескольких узлах, то сортировка параллельно выполняется каждым из узлов, вовлеченных в выполнение запроса. Затем каждый из узлов отправляет свой фрагмент узлу, отвечающему за выдачу данных клиенту, где выполняется слияние полученных фрагментов.

Статистика

Для оценки потенциального числа строк, извлекаемого из таблицы, РСУБД использует статистику. Статистика имеет вид гистограмм для каждой колонки таблицы, где по горизонтали располагается шкала значений, а высотой столбца отмечается оценка числа строк в процентах от общего числа строк.

Таким образом, если из таблицы извлекаются строки со значением колонки C с ограничением [V1, V2], то можно оценить число строк, попадающих в этот интервал. Алгоритм оценки числа извлекаемых строк следующий:

  1. Определить, в какие интервалы гистограммы попадает ограничение [V1, V2];
  2. Найти оценки числа строк Ri для каждого интервала i в процентах.
  3. Если [V1, V2] попадает в некоторый интервал [S1, S2] частично или полностью лежит в интервале, то:
    1. Найти пересечение [V1, V2] и [S1, S2]
    2. Откорректировать число значений в частичном интервале (это либо Ri * (V1 — S2 + 1) / (S1 — S2 + 1), либо Ri * (S1 — V2 + 1) / (S1 — S2 + 1), либо Ri * (V1 — V2 + 1) / (S1 — S2 + 1));
  4. Иначе оценка для интервала равна Ri;
  5. Просуммировать оценки в процентах для всех интервалов;
  6. Перевести оценку в процентах в число строк (см. ниже).

Как правило, СУБД не знает и не может знать точное число строк в таблице (даже для выполнения запроса SELECT COUNT(*) FROM TABLE выполняется сканирование первичного индекса), поскольку в базе могут храниться одновременно несколько образов одной и той же таблицы с различным числом строк. Для оценки числа строк используются следующие данные:

  1. Число страниц в таблице
  2. Длина страницы
  3. Средняя длина строки в таблице

Статистика так же может храниться нарастающим итогом. В этом случае каждый интервал содержит суммарную оценку всех предыдущих интервалов плюс собственную оценку. Для получения оценки числа строк для ограничения [V1, V2] достаточно из оценки интервала, в который попадает V2, вычесть оценку интервала, в который попадает V1.

Сбор статистики для построения гистограмм осуществляется либо специальными командами СУБД, либо фоновыми процессами СУБД. При этом, ввиду того, что база может содержать существенный объём данных, делается выборка меньшего объёма из всей генеральной совокупности строк. Оценка репрезентативности (достоверности) выборки может осуществляться, например, по критерию согласия Колмогорова.

Если данные в таблице существенно изменяются в короткий промежуток времени, то статистика перестает быть актуальной и оптимизатор принимает неверные решения о полном сканировании таблиц. Режим работы базы данных должен быть спланирован таким образом, чтобы поддерживать актуальную статистику, либо не использовать оптимизацию на основе статистики.

См. также

Напишите отзыв о статье "Оптимизация запросов СУБД"

Литература

  • Дейт К. Дж. Введение в системы баз данных. 2001. Из-во: Вильямс. ISBN 5-8459-0138-3
  • Конноли Т., Бегг К. Базы данных. Проектирование, реализация и сопровождение. Теория и практика. Из-во: Вильямс (М., 2003) ISBN 5-8459-0527-3
  • Дональд Кнут. The Art of Computer Programming, Volume 4, Fascicle 0: Introduction to Combinatorial Algorithms and Boolean Functions. — 1 edition (April 27, 2008). — Addison-Wesley Professional, 2008. — С. 240. — ISBN 978-0321534965.

Ссылки

  • [citeseer.ist.psu.edu/bennett91genetic.html Kristin Bennett, Michael C. Ferris, Yannis E. Ioannidis. A Genetic Algorithm for Database Query Optimization. 1991 Proceedings of the Fourth International Conference on Genetic Algorithms]
  • [www.dbis.informatik.hu-berlin.de/fileadmin/research/papers/conferences/1996-gp-stillger.ps Michael Stillger, Myra Spiliopoulou. 1996 Genetic Programming in Database Query Optimization. Institut fur Informatik Humboldt-Universitat zu Berlin]
  • [www.ispras.ru/~gsql/ GNU SQL]

Отрывок, характеризующий Оптимизация запросов СУБД

Проходя мимо буфета, она велела подавать самовар, хотя это было вовсе не время.
Буфетчик Фока был самый сердитый человек из всего дома. Наташа над ним любила пробовать свою власть. Он не поверил ей и пошел спросить, правда ли?
– Уж эта барышня! – сказал Фока, притворно хмурясь на Наташу.
Никто в доме не рассылал столько людей и не давал им столько работы, как Наташа. Она не могла равнодушно видеть людей, чтобы не послать их куда нибудь. Она как будто пробовала, не рассердится ли, не надуется ли на нее кто из них, но ничьих приказаний люди не любили так исполнять, как Наташиных. «Что бы мне сделать? Куда бы мне пойти?» думала Наташа, медленно идя по коридору.
– Настасья Ивановна, что от меня родится? – спросила она шута, который в своей куцавейке шел навстречу ей.
– От тебя блохи, стрекозы, кузнецы, – отвечал шут.
– Боже мой, Боже мой, всё одно и то же. Ах, куда бы мне деваться? Что бы мне с собой сделать? – И она быстро, застучав ногами, побежала по лестнице к Фогелю, который с женой жил в верхнем этаже. У Фогеля сидели две гувернантки, на столе стояли тарелки с изюмом, грецкими и миндальными орехами. Гувернантки разговаривали о том, где дешевле жить, в Москве или в Одессе. Наташа присела, послушала их разговор с серьезным задумчивым лицом и встала. – Остров Мадагаскар, – проговорила она. – Ма да гас кар, – повторила она отчетливо каждый слог и не отвечая на вопросы m me Schoss о том, что она говорит, вышла из комнаты. Петя, брат ее, был тоже наверху: он с своим дядькой устраивал фейерверк, который намеревался пустить ночью. – Петя! Петька! – закричала она ему, – вези меня вниз. с – Петя подбежал к ней и подставил спину. Она вскочила на него, обхватив его шею руками и он подпрыгивая побежал с ней. – Нет не надо – остров Мадагаскар, – проговорила она и, соскочив с него, пошла вниз.
Как будто обойдя свое царство, испытав свою власть и убедившись, что все покорны, но что всё таки скучно, Наташа пошла в залу, взяла гитару, села в темный угол за шкапчик и стала в басу перебирать струны, выделывая фразу, которую она запомнила из одной оперы, слышанной в Петербурге вместе с князем Андреем. Для посторонних слушателей у ней на гитаре выходило что то, не имевшее никакого смысла, но в ее воображении из за этих звуков воскресал целый ряд воспоминаний. Она сидела за шкапчиком, устремив глаза на полосу света, падавшую из буфетной двери, слушала себя и вспоминала. Она находилась в состоянии воспоминания.
Соня прошла в буфет с рюмкой через залу. Наташа взглянула на нее, на щель в буфетной двери и ей показалось, что она вспоминает то, что из буфетной двери в щель падал свет и что Соня прошла с рюмкой. «Да и это было точь в точь также», подумала Наташа. – Соня, что это? – крикнула Наташа, перебирая пальцами на толстой струне.
– Ах, ты тут! – вздрогнув, сказала Соня, подошла и прислушалась. – Не знаю. Буря? – сказала она робко, боясь ошибиться.
«Ну вот точно так же она вздрогнула, точно так же подошла и робко улыбнулась тогда, когда это уж было», подумала Наташа, «и точно так же… я подумала, что в ней чего то недостает».
– Нет, это хор из Водоноса, слышишь! – И Наташа допела мотив хора, чтобы дать его понять Соне.
– Ты куда ходила? – спросила Наташа.
– Воду в рюмке переменить. Я сейчас дорисую узор.
– Ты всегда занята, а я вот не умею, – сказала Наташа. – А Николай где?
– Спит, кажется.
– Соня, ты поди разбуди его, – сказала Наташа. – Скажи, что я его зову петь. – Она посидела, подумала о том, что это значит, что всё это было, и, не разрешив этого вопроса и нисколько не сожалея о том, опять в воображении своем перенеслась к тому времени, когда она была с ним вместе, и он влюбленными глазами смотрел на нее.
«Ах, поскорее бы он приехал. Я так боюсь, что этого не будет! А главное: я стареюсь, вот что! Уже не будет того, что теперь есть во мне. А может быть, он нынче приедет, сейчас приедет. Может быть приехал и сидит там в гостиной. Может быть, он вчера еще приехал и я забыла». Она встала, положила гитару и пошла в гостиную. Все домашние, учителя, гувернантки и гости сидели уж за чайным столом. Люди стояли вокруг стола, – а князя Андрея не было, и была всё прежняя жизнь.
– А, вот она, – сказал Илья Андреич, увидав вошедшую Наташу. – Ну, садись ко мне. – Но Наташа остановилась подле матери, оглядываясь кругом, как будто она искала чего то.
– Мама! – проговорила она. – Дайте мне его , дайте, мама, скорее, скорее, – и опять она с трудом удержала рыдания.
Она присела к столу и послушала разговоры старших и Николая, который тоже пришел к столу. «Боже мой, Боже мой, те же лица, те же разговоры, так же папа держит чашку и дует точно так же!» думала Наташа, с ужасом чувствуя отвращение, подымавшееся в ней против всех домашних за то, что они были всё те же.
После чая Николай, Соня и Наташа пошли в диванную, в свой любимый угол, в котором всегда начинались их самые задушевные разговоры.


– Бывает с тобой, – сказала Наташа брату, когда они уселись в диванной, – бывает с тобой, что тебе кажется, что ничего не будет – ничего; что всё, что хорошее, то было? И не то что скучно, а грустно?
– Еще как! – сказал он. – У меня бывало, что всё хорошо, все веселы, а мне придет в голову, что всё это уж надоело и что умирать всем надо. Я раз в полку не пошел на гулянье, а там играла музыка… и так мне вдруг скучно стало…
– Ах, я это знаю. Знаю, знаю, – подхватила Наташа. – Я еще маленькая была, так со мной это бывало. Помнишь, раз меня за сливы наказали и вы все танцовали, а я сидела в классной и рыдала, никогда не забуду: мне и грустно было и жалко было всех, и себя, и всех всех жалко. И, главное, я не виновата была, – сказала Наташа, – ты помнишь?
– Помню, – сказал Николай. – Я помню, что я к тебе пришел потом и мне хотелось тебя утешить и, знаешь, совестно было. Ужасно мы смешные были. У меня тогда была игрушка болванчик и я его тебе отдать хотел. Ты помнишь?
– А помнишь ты, – сказала Наташа с задумчивой улыбкой, как давно, давно, мы еще совсем маленькие были, дяденька нас позвал в кабинет, еще в старом доме, а темно было – мы это пришли и вдруг там стоит…
– Арап, – докончил Николай с радостной улыбкой, – как же не помнить? Я и теперь не знаю, что это был арап, или мы во сне видели, или нам рассказывали.
– Он серый был, помнишь, и белые зубы – стоит и смотрит на нас…
– Вы помните, Соня? – спросил Николай…
– Да, да я тоже помню что то, – робко отвечала Соня…
– Я ведь спрашивала про этого арапа у папа и у мама, – сказала Наташа. – Они говорят, что никакого арапа не было. А ведь вот ты помнишь!
– Как же, как теперь помню его зубы.
– Как это странно, точно во сне было. Я это люблю.
– А помнишь, как мы катали яйца в зале и вдруг две старухи, и стали по ковру вертеться. Это было, или нет? Помнишь, как хорошо было?
– Да. А помнишь, как папенька в синей шубе на крыльце выстрелил из ружья. – Они перебирали улыбаясь с наслаждением воспоминания, не грустного старческого, а поэтического юношеского воспоминания, те впечатления из самого дальнего прошедшего, где сновидение сливается с действительностью, и тихо смеялись, радуясь чему то.
Соня, как и всегда, отстала от них, хотя воспоминания их были общие.
Соня не помнила многого из того, что они вспоминали, а и то, что она помнила, не возбуждало в ней того поэтического чувства, которое они испытывали. Она только наслаждалась их радостью, стараясь подделаться под нее.
Она приняла участие только в том, когда они вспоминали первый приезд Сони. Соня рассказала, как она боялась Николая, потому что у него на курточке были снурки, и ей няня сказала, что и ее в снурки зашьют.
– А я помню: мне сказали, что ты под капустою родилась, – сказала Наташа, – и помню, что я тогда не смела не поверить, но знала, что это не правда, и так мне неловко было.
Во время этого разговора из задней двери диванной высунулась голова горничной. – Барышня, петуха принесли, – шопотом сказала девушка.
– Не надо, Поля, вели отнести, – сказала Наташа.
В середине разговоров, шедших в диванной, Диммлер вошел в комнату и подошел к арфе, стоявшей в углу. Он снял сукно, и арфа издала фальшивый звук.
– Эдуард Карлыч, сыграйте пожалуста мой любимый Nocturiene мосье Фильда, – сказал голос старой графини из гостиной.
Диммлер взял аккорд и, обратясь к Наташе, Николаю и Соне, сказал: – Молодежь, как смирно сидит!
– Да мы философствуем, – сказала Наташа, на минуту оглянувшись, и продолжала разговор. Разговор шел теперь о сновидениях.
Диммлер начал играть. Наташа неслышно, на цыпочках, подошла к столу, взяла свечу, вынесла ее и, вернувшись, тихо села на свое место. В комнате, особенно на диване, на котором они сидели, было темно, но в большие окна падал на пол серебряный свет полного месяца.
– Знаешь, я думаю, – сказала Наташа шопотом, придвигаясь к Николаю и Соне, когда уже Диммлер кончил и всё сидел, слабо перебирая струны, видимо в нерешительности оставить, или начать что нибудь новое, – что когда так вспоминаешь, вспоминаешь, всё вспоминаешь, до того довоспоминаешься, что помнишь то, что было еще прежде, чем я была на свете…
– Это метампсикова, – сказала Соня, которая всегда хорошо училась и все помнила. – Египтяне верили, что наши души были в животных и опять пойдут в животных.
– Нет, знаешь, я не верю этому, чтобы мы были в животных, – сказала Наташа тем же шопотом, хотя музыка и кончилась, – а я знаю наверное, что мы были ангелами там где то и здесь были, и от этого всё помним…
– Можно мне присоединиться к вам? – сказал тихо подошедший Диммлер и подсел к ним.
– Ежели бы мы были ангелами, так за что же мы попали ниже? – сказал Николай. – Нет, это не может быть!
– Не ниже, кто тебе сказал, что ниже?… Почему я знаю, чем я была прежде, – с убеждением возразила Наташа. – Ведь душа бессмертна… стало быть, ежели я буду жить всегда, так я и прежде жила, целую вечность жила.
– Да, но трудно нам представить вечность, – сказал Диммлер, который подошел к молодым людям с кроткой презрительной улыбкой, но теперь говорил так же тихо и серьезно, как и они.
– Отчего же трудно представить вечность? – сказала Наташа. – Нынче будет, завтра будет, всегда будет и вчера было и третьего дня было…
– Наташа! теперь твой черед. Спой мне что нибудь, – послышался голос графини. – Что вы уселись, точно заговорщики.
– Мама! мне так не хочется, – сказала Наташа, но вместе с тем встала.
Всем им, даже и немолодому Диммлеру, не хотелось прерывать разговор и уходить из уголка диванного, но Наташа встала, и Николай сел за клавикорды. Как всегда, став на средину залы и выбрав выгоднейшее место для резонанса, Наташа начала петь любимую пьесу своей матери.
Она сказала, что ей не хотелось петь, но она давно прежде, и долго после не пела так, как она пела в этот вечер. Граф Илья Андреич из кабинета, где он беседовал с Митинькой, слышал ее пенье, и как ученик, торопящийся итти играть, доканчивая урок, путался в словах, отдавая приказания управляющему и наконец замолчал, и Митинька, тоже слушая, молча с улыбкой, стоял перед графом. Николай не спускал глаз с сестры, и вместе с нею переводил дыхание. Соня, слушая, думала о том, какая громадная разница была между ей и ее другом и как невозможно было ей хоть на сколько нибудь быть столь обворожительной, как ее кузина. Старая графиня сидела с счастливо грустной улыбкой и слезами на глазах, изредка покачивая головой. Она думала и о Наташе, и о своей молодости, и о том, как что то неестественное и страшное есть в этом предстоящем браке Наташи с князем Андреем.
Диммлер, подсев к графине и закрыв глаза, слушал.
– Нет, графиня, – сказал он наконец, – это талант европейский, ей учиться нечего, этой мягкости, нежности, силы…
– Ах! как я боюсь за нее, как я боюсь, – сказала графиня, не помня, с кем она говорит. Ее материнское чутье говорило ей, что чего то слишком много в Наташе, и что от этого она не будет счастлива. Наташа не кончила еще петь, как в комнату вбежал восторженный четырнадцатилетний Петя с известием, что пришли ряженые.
Наташа вдруг остановилась.
– Дурак! – закричала она на брата, подбежала к стулу, упала на него и зарыдала так, что долго потом не могла остановиться.
– Ничего, маменька, право ничего, так: Петя испугал меня, – говорила она, стараясь улыбаться, но слезы всё текли и всхлипывания сдавливали горло.
Наряженные дворовые, медведи, турки, трактирщики, барыни, страшные и смешные, принеся с собою холод и веселье, сначала робко жались в передней; потом, прячась один за другого, вытеснялись в залу; и сначала застенчиво, а потом всё веселее и дружнее начались песни, пляски, хоровые и святочные игры. Графиня, узнав лица и посмеявшись на наряженных, ушла в гостиную. Граф Илья Андреич с сияющей улыбкой сидел в зале, одобряя играющих. Молодежь исчезла куда то.
Через полчаса в зале между другими ряжеными появилась еще старая барыня в фижмах – это был Николай. Турчанка был Петя. Паяс – это был Диммлер, гусар – Наташа и черкес – Соня, с нарисованными пробочными усами и бровями.
После снисходительного удивления, неузнавания и похвал со стороны не наряженных, молодые люди нашли, что костюмы так хороши, что надо было их показать еще кому нибудь.
Николай, которому хотелось по отличной дороге прокатить всех на своей тройке, предложил, взяв с собой из дворовых человек десять наряженных, ехать к дядюшке.
– Нет, ну что вы его, старика, расстроите! – сказала графиня, – да и негде повернуться у него. Уж ехать, так к Мелюковым.
Мелюкова была вдова с детьми разнообразного возраста, также с гувернантками и гувернерами, жившая в четырех верстах от Ростовых.
– Вот, ma chere, умно, – подхватил расшевелившийся старый граф. – Давай сейчас наряжусь и поеду с вами. Уж я Пашету расшевелю.
Но графиня не согласилась отпустить графа: у него все эти дни болела нога. Решили, что Илье Андреевичу ехать нельзя, а что ежели Луиза Ивановна (m me Schoss) поедет, то барышням можно ехать к Мелюковой. Соня, всегда робкая и застенчивая, настоятельнее всех стала упрашивать Луизу Ивановну не отказать им.
Наряд Сони был лучше всех. Ее усы и брови необыкновенно шли к ней. Все говорили ей, что она очень хороша, и она находилась в несвойственном ей оживленно энергическом настроении. Какой то внутренний голос говорил ей, что нынче или никогда решится ее судьба, и она в своем мужском платье казалась совсем другим человеком. Луиза Ивановна согласилась, и через полчаса четыре тройки с колокольчиками и бубенчиками, визжа и свистя подрезами по морозному снегу, подъехали к крыльцу.
Наташа первая дала тон святочного веселья, и это веселье, отражаясь от одного к другому, всё более и более усиливалось и дошло до высшей степени в то время, когда все вышли на мороз, и переговариваясь, перекликаясь, смеясь и крича, расселись в сани.
Две тройки были разгонные, третья тройка старого графа с орловским рысаком в корню; четвертая собственная Николая с его низеньким, вороным, косматым коренником. Николай в своем старушечьем наряде, на который он надел гусарский, подпоясанный плащ, стоял в середине своих саней, подобрав вожжи.
Было так светло, что он видел отблескивающие на месячном свете бляхи и глаза лошадей, испуганно оглядывавшихся на седоков, шумевших под темным навесом подъезда.
В сани Николая сели Наташа, Соня, m me Schoss и две девушки. В сани старого графа сели Диммлер с женой и Петя; в остальные расселись наряженные дворовые.
– Пошел вперед, Захар! – крикнул Николай кучеру отца, чтобы иметь случай перегнать его на дороге.
Тройка старого графа, в которую сел Диммлер и другие ряженые, визжа полозьями, как будто примерзая к снегу, и побрякивая густым колокольцом, тронулась вперед. Пристяжные жались на оглобли и увязали, выворачивая как сахар крепкий и блестящий снег.
Николай тронулся за первой тройкой; сзади зашумели и завизжали остальные. Сначала ехали маленькой рысью по узкой дороге. Пока ехали мимо сада, тени от оголенных деревьев ложились часто поперек дороги и скрывали яркий свет луны, но как только выехали за ограду, алмазно блестящая, с сизым отблеском, снежная равнина, вся облитая месячным сиянием и неподвижная, открылась со всех сторон. Раз, раз, толконул ухаб в передних санях; точно так же толконуло следующие сани и следующие и, дерзко нарушая закованную тишину, одни за другими стали растягиваться сани.
– След заячий, много следов! – прозвучал в морозном скованном воздухе голос Наташи.
– Как видно, Nicolas! – сказал голос Сони. – Николай оглянулся на Соню и пригнулся, чтоб ближе рассмотреть ее лицо. Какое то совсем новое, милое, лицо, с черными бровями и усами, в лунном свете, близко и далеко, выглядывало из соболей.