in Профессиональное

Volume testing: быстрое наполнение тестовых таблиц в MySQL

Представим, что вы создаете новое приложение и вам нужно протестировать его на большом объеме данных (volume testing). В этом случае вы можете взять уже готовые данные, или же подготовить их самостоятельно. Если у вас есть набор данных для тестов достаточного объема – это просто замечательно, но чаще всего данных нужного объема у вас не будет и вам будет нужен способ для быстрого их создания. Ниже будут перечислены три способа создания больших наборов данных простых типов (чисел, слов, дат).

Числа

Создавать большие наборы числовых данных совсем не сложно.

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

drop table if exists numbers;
create table numbers ( id int not null primary key);

delimiter $$

drop procedure if exists fill_numbers $$
create procedure fill_numbers()
deterministic
begin
  declare counter int default 1;
  insert into numbers values (1);
  while counter < 1000000
  do
      insert into numbers (id)
          select id + counter
          from numbers;
      select count(*) into counter from numbers;
      select counter;
  end while;
end $$
delimiter ;

call fill_numbers();

Данный способ намного более быстрый, нежели прямая вставка 1’000’000 строк. Мы вставляем в таблицу всего одну строку, и потом дублируем таблицу 20 раз, пока не получим 1’048’576 строк (220). Эта операция занимает менее 8 секунд на ноутбуке автора, который намного менее мощный, нежели средний сервер (на момент написания статьи – 2006г. // hudson). Даже если вы не хотите использовать хранимую процедуру, вы можете вручную вставить 1 строку и выполнить 20 раз следующий запрос:

insert into
    numbers (id)
select
    id + (select count(*) from numbers)
from
    numbers; 

select count(*) from numbers;

Эта процедура не займет у вас больше 30 секунд.

Слова

Если вам нужно работать с большим объемом уникальных строковых данных, вы опять таки, можете написать программу на любом языке, однако это будет совсем не быстро (процесс вставки достаточно медленный). Наиболее быстрый способ – загрузить готовый список слов из файла. Все Unix системы содержат списки слов – от нескольких тысяч, до полумиллиона. Если вдруг у вас такого не оказалось, вы можете скачать его из множества доступных источников или собрать самостоятельно (для начала можете посмотреть здесь: ftp://ftp.cerias.purdue.edu/pub/dict/ или здесь ftp://ftp.ox.ac.uk/pub/wordlists/).

И, наконец, покажем как имея на руках файл с примерно полумиллионом слов /usr/share/dict/words, вы сможете наполнить тестовую таблицу:

drop table if exists words;
create table words (
  id int not null auto_increment primary key,
  t varchar(50) not null
);

load data local infile '/usr/share/dict/words'
  into table words (t);

Query OK, 518584 rows affected (4.94 sec)
Records: 518584  Deleted: 0  Skipped: 0  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|   518584 |
+----------+
1 row in set (0.04 sec)

Быстро, не правда ли? Но постойте-ка, у нас пока что есть только пол миллиона записей (ну… чуть больше). Так как нам нужны уникальные слова, мы можем попросить базу данных, изменить порядок букв в уже существующих (reverse()):

insert into words (t) select reverse(t) from words;
Query OK, 518584 rows affected (3.98 sec)
Records: 518584  Duplicates: 0  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|  1037168 |
+----------+

Вот так вот! Но мы все еще можем сомневаться, уникальны ли эти слова, так как изменение порядка букв одного слова может превратить его в другое (например mood <–> doom). Т.о. чтобы считать нашу задачу завершенной, нужно добавить UNIQUE индекс с опцией IGNORE, что позволит исключить дубликаты:

alter ignore table words add unique key (t);
Query OK, 1037168 rows affected (46.69 sec)
Records: 1037168  Duplicates: 5791  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|  1031377 |
+----------+

Вот так – миллион слов, не напрягаясь )

Даты

Наконец, давайте посмотрим, как можно быстро и просто создавать большие наборы дат. Фактически, миллион разных дат вам вряд ли понадобится, т.к. миллион дней это более 2700 лет. Т.о. только даты будут покрывать диапазон от 1000 до 10000 дней, вряд ли больше. Если же вам нужен миллион записей, то впору поговорить не о DATE а о DATETIME с интервалами в часы, минуты или даже секунды. Никто не запрещает вам использовать эту технику для создания сотни уникальных DATE, но при этом иметь в таблице что-то около миллиона записей. Итак, если мы хотим создать записи с минутным интервалом то нужно выполнить следующий код:

drop table if exists dates;
create table dates (
  id int(11) not null auto_increment primary key,
  dt datetime not NULL
) engine=myisam;

delimiter $$

drop procedure if exists make_dates $$
CREATE PROCEDURE make_dates( max_recs int)
begin
  declare start_dt datetime;
  declare numrecs int default 1;
  set start_dt = date_format( now() - interval max_recs minute, '%Y-%m-%d %H:%i:00');

  insert into dates (dt) values (start_dt );

  while numrecs < max_recs
  do
      insert into dates (dt)
          select dt + interval ( numrecs ) minute
          from dates;
      select count(*) into numrecs from dates;
      select numrecs;
  end while;
end $$

delimiter ;

Выглядит знакомо, не правда ли? ) Неудивительно, ведь ту же технику мы использовали для заполнения таблицы с числами. Хотя в отличие от чисел, здесь мы использовали число записей в таблице для вычисления интервала в минутах между существующими записями и теми записями, которые будут добавлены в этой итерации. Но мы также дублируем таблицу 20 раз, чтобы получить 1’000’000 записей.

call make_dates( 1000000 );
+---------+
| numrecs |
+---------+
|       2 |
+---------+
1 row in set (0.02 sec)

+---------+
| numrecs |
+---------+
|       4 |
+---------+
1 row in set (0.02 sec)

# ... 16 more counts

+---------+
| numrecs |
+---------+
|  524288 |
+---------+
1 row in set (5.99 sec)

+---------+
| numrecs |
+---------+
| 1048576 |
+---------+
1 row in set (10.18 sec)

select count(*), min(dt), max(dt) from dates;
+----------+---------------------+---------------------+
| count(*) | min(dt)             | max(dt)             |
+----------+---------------------+---------------------+
|  1048576 | 2004-07-07 13:57:00 | 2006-07-05 18:12:00 |
+----------+---------------------+---------------------+

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

Заключение


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

p.s. как и обещал, это перевод статьи http://datacharmer.blogspot.com/2006/06/filling-test-tables-quickly.html

Write a Comment

Comment

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.