Как оптимизировать лимитный запрос для быстрого доступа к данным из огромной таблицы? - PullRequest
0 голосов
/ 12 ноября 2018

Я пытаюсь получить данные из таблицы размером 9 ГБ + и содержащей миллионы записей.Я заполняю DataTable этими данными.Я получаю записи в виде фрагментов из таблицы, т.е. по 10 на страницу с помощью запросов Ajax и SQL Limit.

разбиение на страницы

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

 public static function getAllEvaluationsWithNameForDataTable($start){
        $queryBuilder = new Builder();

        return  $queryBuilder
            ->from(array('e' =>  static::class))
            ->leftJoin('Cx\Framework\Models\Common\User\CxUser',  'e.cx_hc_user_id = u.id', 'u')
            ->columns('e.id, e.first_name, u.initials as assigned_coach, e.gender, e.email, e.phone, e.age, e.version, e.evaluation_status, e.ip_address, e.date_created, e.date_updated')
            ->orderBy('e.id asc')
            ->limit(10, $start)
            ->getQuery()
            ->execute()
            ->toArray();
}

Функция / контроллер PHP:

public function getEvaluationsAction() {
        // Enable Json response
        $this->setJsonResponse();
        // This action can be called only via ajax
        $this->requireAjax();

        // Forward to access denied if current user is not allowed to view evaluation details
        if (!$this->CxAuth->currentUserIsAllowedTo('VIEW', CxEbEvaluation::getClassResourceName()))
            return $this->forwardToAccessDeniedError();

        if(isset($_GET['start'])){
            $start = $this->request->get('start');
        }else{
            $start = 10;
        }

        $recordsTotal = count(CxEbEvaluation::getAllForDataTable(array('id')));

        //Get Evaluations from DB
        $evaluation_quizzes = CxEbEvaluation::getAllEvaluationsWithNameForDataTable(intval($start));

        //for getting base URL
        $url = new Url();

        $data = array();

        foreach ($evaluation_quizzes as $key => $quiz) {
            $data[ $key ][ 'id' ] = $quiz[ 'id' ];
            $data[ $key ][ 'first_name' ] = $quiz[ 'first_name' ];
            if($quiz[ 'assigned_coach' ]){
                $data[ $key ][ 'assigned_coach' ] = $quiz['assigned_coach'];
            }else{
                $data[ $key ][ 'assigned_coach' ] = "Not assigned";
            }

            $data[ $key ][ 'gender' ] = $quiz[ 'gender' ];
            $data[ $key ][ 'email' ] = $quiz[ 'email' ];
            $data[ $key ][ 'phone' ] = $quiz[ 'phone' ];
            $data[ $key ][ 'age' ] = $quiz[ 'age' ];
            $data[ $key ][ 'version' ] = $quiz[ 'version' ];
            $data[ $key ][ 'quiz' ] =  $url->get('/admin/get-evaluation-quiz-by-id');
            $data[ $key ][ 'manage-notes-messages-and-calls' ] =  $url->get('/admin/manage-notes-messages-and-calls');
            $data[ $key ][ 'date_created' ] = date("m/d/Y H:i:s", $quiz[ 'date_created' ]);
            $data[ $key ][ 'evaluation_status' ] = $quiz[ 'evaluation_status' ];
        }
        // Return data array
        return array(
            "recordsTotal"    => $recordsTotal,
            "recordsFiltered" => $recordsTotal ,
            "data"            => $data //How To Retrieve This Data
        );
        // Return data
    }

Javascript:

cx.common.data.cxAdminDataTables.EbEvaluation = $CxRecordsTable.cxAdminDataTable({
        ajaxUrl: '<?php echo $this->CxHelper->Route('eb-admin-get-evaluations')?>' + eqQuizIdQueryString,
        serverSide: true,
        processing: true,
        recordsFiltered :true,
        columns: [
            cx.common.admin.tableEditColumn('id',{ delete: true }),
            { data: 'first_name' },
            { data: 'assigned_coach' },
            { data: 'gender' },
            { data: 'email' },
            { data: 'phone' },
            { data: 'age' },
            cx.common.admin.tableLinkColumn('quiz', quizLinkOptions),
            cx.common.admin.tableEditColumn('id', healthCoachLinkOptions),
            cx.common.admin.tableLinkColumn('manage-notes-messages-and-calls', manageNotesMessagesAndCalls),
            { data: 'date_created' },
            cx.common.admin.tableSwitchableColumn('evaluation_status', {
                editable: true,
                createdCell: function (td, cellData, rowData, row, col){
                    $(td).data('evaluation-status-id', rowData.id);
                },
                onText: 'Complete',
                offText: 'In progress'
            })
        ],
        toolbarOptions:{
            enabled: false
        },          success: function (data) {
                            cx.common.data.cxAdminDataTables.EbEvaluation.cxAdminDataTable("reloadAjax");
                        }
                    });
                }
                else {
                    $row.removeClass('alert');
                }
            });
        }
    });

Я надеюсь, что вопросЧисто.Если что-то еще потребуется, просто сообщите мне, я предоставлю.

(из комментария)

SELECT  e.id` AS id, e.first_name AS first_name,
        u.initials AS assigned_coach,
        e.gender AS gender, e.email AS email, e.phone AS phone,
        e.age AS age, e.version AS version,
        e.evaluation_status AS evaluation_status,
        e.ip_address AS ip_address, e.date_created AS date_created,
        e.date_updated AS date_updated
    FROM  evaluation_client AS e
    LEFT JOIN  cx_user AS u  ON e.cx_hc_user_id = u.id
    ORDER BY  e.id ASC
    LIMIT  :APL0 OFFSET, :APL1

Ответы [ 3 ]

0 голосов
/ 12 ноября 2018

Значение Почему MYSQL с более высоким смещением LIMIT замедляет запрос? Вопросы и ответы, связанные с Masivuye Cokile, а также предоставленная там ссылка https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ содержат превосходное изложение того, почему большиеЗапросы смещения медленные.По сути, для LIMIT 150000, 10 MySQL по-прежнему сканирует все 150000 строк, даже если позднее их удаляет.Чтобы ускорить его, вы можете:

  • использовать последовательную нумерацию страниц, то есть «показывать 10 записей после ID #N», которая работает очень быстро и является хорошей альтернативой, но отбрасывает фактический номер страницы;у ваших пользователей останутся ссылки «следующий / предыдущий» и / или приблизительный номер страницы, который вы можете рассчитать с помощью запроса count.
  • или создать индекс для id, а затем принудительно выполнить mysql для выполнения запроса.поиск только по индексу.

Для второго подхода вам придется переписать запрос с

SELECT ... 
  FROM table t 
WHERE ...
ORDER by t.id ASC
LIMIT 150000, 10

на

SELECT  ...
  FROM (
        SELECT  id
        FROM    table
        ORDER BY
                id ASC
        LIMIT 150000, 10
        ) o
JOIN table t
  ON t.id = o.id
WHERE ...
ORDER BY t.id ASC

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

SELECT id 
  FROM table 
 ORDER BY id ASC 
 LIMIT 150000, 1

, а затем использовать указанный идентификатор для получения фактических данных:

SELECT ...
  FROM table
 WHERE id >= $id
   AND ...
 ORDER BY id ASC
 LIMIT 0, 10
0 голосов
/ 25 ноября 2018

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

Источник, где я нашел решение @ http://dbscience.blogspot.com/2008/08/can-timestamp-be-slower-than-datetime.html

0 голосов
/ 12 ноября 2018

Узор SELECT whatever FROM vast_table ORDER BY something LIMIT 10 large_number является печально известным антипаттерном. Зачем? Потому что он должен проверить очень много строк, чтобы вернуть несколько.

Если ваше значение id является первичным ключом (или любым индексированным столбцом), вы можете разбить на страницы

SELECT whatever FROM vast_table WHERE id BETWEEN large_value AND large_value+9 ORDER BY id;

Или вы можете попробовать

SELECT whatever FROM vast_table WHERE id >= large_value ORDER BY id LIMIT 10;

Это не разбивает на страницы, если в ваших значениях id есть пробелы. Но он работает сносно хорошо.

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