2013-06-21 3 views
6

Próbuję użyć Paginatora ZF2 na niektórych dużych zestawach rekordów (około 10 milionów w najgorszym przypadku bez filtra wyszukiwania). Moje tabele są w formacie InnoDB, co, jak rozumiem, nie obejmuje dokładnego liczenia jako części metadanych.Abysmal Performance for Count() za pomocą Paginarza ZF2 na tabelach InnoDB

Zdaję sobie sprawę, że mogę rozszerzyć klasę Zend \ Paginator \ Adapter \ DbSelect i zaimplementować własną metodę count(), która wykorzystuje dane liczbowe, które ręcznie zapisuję w innej tabeli, ale nie jestem pewien, jak przechowywać liczniki dla wszystkie możliwe permutacje wyszukiwań, które można wykonać.

Domyślna ZF2 DbSelect adapter używa tej metody:

<?php 
public function count() 
{ 
    if ($this->rowCount !== null) { 
     return $this->rowCount; 
    } 

    $select = clone $this->select; 
    $select->reset(Select::LIMIT); 
    $select->reset(Select::OFFSET); 
    $select->reset(Select::ORDER); 

    $countSelect = new Select; 
    $countSelect->columns(array('c' => new Expression('COUNT(1)'))); 
    $countSelect->from(array('original_select' => $select)); 

    $statement = $this->sql->prepareStatementForSqlObject($countSelect); 
    $result = $statement->execute(); 
    $row  = $result->current(); 

    $this->rowCount = $row['c']; 

    return $this->rowCount; 
} 
?> 

Oto bardzo prosty przykład kwerendy, która metoda daje mi:

SELECT 
    COUNT(1) AS `c` 
FROM 
    (
     SELECT 
      `contacts`.`id` AS `id`, 
      `contacts`.`firstname` AS `firstname`, 
      `contacts`.`middlename` AS `middlename`, 
      `contacts`.`lastname` AS `lastname`, 
      `contacts`.`gender` AS `gender` 
     FROM 
      `contacts` 
     WHERE 
      `contacts`.`trash` = '0' 
    ) AS `original_select` 

Nie jestem pewien, co wydajność byłaby na MyISAM tabele, ale to mi się nie uda, ponieważ pochłania całą wolną przestrzeń w instancji Amazon RDS (25 GB, db.m1.small), na której jest uruchomiona. Dla porównania, uruchomienie tylko wewnętrznego (oryginalnego) zapytania kończy się w ciągu 100 sekund (z pewnością nie jest dobre) i zwraca 7.39 milionów rekordów.

Oto EXPLAIN z kwerendy wewnętrznej (wyjaśniania na hrabiego też umiera z powodu przestrzeni dyskowej na serwerze RDS):

 
+----+-------------+----------+------+---------------+-------+---------+-------+---------+-------+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
+----+-------------+----------+------+---------------+-------+---------+-------+---------+-------+ 
| 1 | SIMPLE  | contacts | ref | trash   | trash | 1  | const | 3441317 |  | 
+----+-------------+----------+------+---------------+-------+---------+-------+---------+-------+ 
1 rows in set (0.04 sec) 

Czy jest coś, co można zrobić, aby dostroić to lepiej? Czy sposób, w jaki Paginator obsługuje ZF2, jest niekompatybilny w taki sposób, w jaki robi to InnoDB? W jaki sposób inni będą obsługiwać zapisy w pamięci podręcznej wszystkich możliwych zapytań, jeśli zezwalamy na wyszukiwanie w większości pól w bazie danych?

góry dzięki ...

+0

Ten problem jest związany z InnoDB, MyISAM nie ma tego problemu, ponieważ śledzi liczbę wierszy w tabeli w swoich metadanych. Najlepiej jest po prostu poprawić zapytanie w interfejsie bazy danych i zobaczyć, jaki jest jego efekt. Zamiast COUNT (1) możesz wypróbować na przykład COUNT (id) lub powiedzieć MySQL, aby używał określonego indeksu używając mysql> select count (1) from ... use index (your_index) – Ruben

+0

** COUNT (1) **: 15 min 36sek; ** COUNT (identyfikator) **: 18 minut 8,83 s – jcq

Odpowiedz

0

Jeśli używasz tej kwerendy Zamiast:

SELECT c from 
( 
    SELECT COUNT(1) AS c 
    from contacts 
    where trash = '0' 
) AS original_select 
2

Nie trzeba select z oryginalnego zapytania - to pochłania swoją pamięć/miejsca na dysku!

SELECT count(1) AS `c` 
FROM (
    SELECT 1 
    FROM `contacts` 
    WHERE `trash` = 0 
) AS `original_select` 

Poza tym:

  • Zakładając śmieci jest tylko wartość logiczna, sprawiają, że logiczna pustych kolumna i szukać int lub logiczną true/false

    ALTER TABLE `contacts` CHANGE `trash` `trash` TINYINT(1) NOT NULL 
    
  • Pamiętaj, aby zindeksować kolumnę śmieci

    ALTER TABLE `contacts` ADD INDEX `TRASH` (`trash`) 
    

Co więcej:

  • Pagination dużych zestawów wyników niekoniecznie wymagają dokładnej liczby: Powiedzmy więc pokazujemy 100 wpisów na stronie, nie musimy 100000 pojedyncze przyciski strona N. Zamiast tego oblicz stronę za pomocą przesunięcia i limitu i po prostu pokaż pojedyncze przyciski dla np. poprzednie/następne 10 stron i połącz je z kilkoma przyciskami "pokaż następne/poprzednie 10 stron".

  • Gdy potrzebujesz możliwości "przejścia na ostatnią stronę", dlaczego nie użyć czegoś takiego jak polecenie DESC, aby osiągnąć coś podobnego.

  • Czy naprawdę istnieje sytuacja, w której ktoś będzie się przedzierać przez twoje 10-metrowe rzędy? Może dostarczyć zaawansowane filtry, aby pomóc użytkownikowi znaleźć to, czego potrzebuje.