Почему этот оператор select такой медленный? - PullRequest
0 голосов
/ 14 мая 2018

Этот оператор выбора выполняется очень медленно. Требуется более 10 секунд, чтобы завершить выполнение. Может быть намного дольше, но я не могу знать, потому что время ожидания подключения к MySQL. Это отдельная проблема.

Вот код:

SELECT 
    f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
    families f,
    children c,
    transactions t
WHERE
    f.companyid = 1170 AND f.id = t.familyid
        AND f.id = c.familyid
        AND t.transactiontype = 'P'
        AND t.taxdeductible = 'Y'
        AND YEAR(t.date) = 2017
        AND status = 'A'
        OR f.id = 9779432
GROUP BY f.id
ORDER BY name;

У меня есть индексы на семействах:.

Есть ли какая-либо причина, по которой он будет выполнять полное сканирование таблицы, несмотря на мои индексы? Или есть другая причина, почему этот запрос выполняется медленно?

РЕДАКТИРОВАТЬ: Заполнить некоторые пробелы в соответствии с комментариями ниже:

В дочерней таблице содержится 17 МБ данных в 73 000 строк. Таблица семейств содержит 6 МБ данных в 56 000 строк. Таблица транзакций содержит 83 МБ данных в 980 000 строк.

СТОЛ ДЛЯ ДЕТЕЙ

CREATE TABLE `children` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `familyid` int(10) unsigned DEFAULT '0',
  `companyid` int(11) DEFAULT '0',
  `picture` varchar(250) DEFAULT NULL,
  `stockpicture` varchar(1) DEFAULT 'N',
  `firstname` varchar(250) DEFAULT NULL,
  `lastname` varchar(250) DEFAULT NULL,
  `nickname` varbinary(250) DEFAULT NULL,
  `birthdate` date NOT NULL DEFAULT '0000-00-00',
  `usecustomfee` varchar(1) NOT NULL DEFAULT 'N',
  `usecustomproviderfee` varchar(1) NOT NULL DEFAULT 'N',
  `customfee` decimal(10,2) DEFAULT '0.00',
  `customfeetypecode` varchar(45) DEFAULT 'MONTH',
  `customproviderfee` decimal(10,2) DEFAULT '0.00',
  `customproviderfeetypecode` varchar(45) DEFAULT 'MONTH',
  `usecustomchargeitem` varchar(1) DEFAULT 'N',
  `customchargeitem` int(11) DEFAULT '0',
  `dailyrate` decimal(10,2) DEFAULT '55.00',
  `startdate` date DEFAULT NULL,
  `enddate` date DEFAULT NULL,
  `subsidynotrequired` char(1) NOT NULL DEFAULT 'Y',
  `subsidychildid` varchar(250) DEFAULT NULL,
  `subsidyapplicantid` varchar(250) DEFAULT NULL,
  `subsidynote` text,
  `waitingsince` date DEFAULT NULL,
  `waitingroom` int(11) DEFAULT NULL,
  `waitingtype` varchar(1) DEFAULT 'F',
  `preferredstart` date DEFAULT NULL,
  `registrationdate` date DEFAULT NULL,
  `groupid` int(11) NOT NULL DEFAULT '0',
  `providerisparent` varchar(1) NOT NULL DEFAULT 'N',
  `attendingschool` char(1) NOT NULL DEFAULT 'N',
  `schoolname` varchar(250) DEFAULT NULL,
  `liveswithmother` char(1) NOT NULL DEFAULT 'Y',
  `liveswithfather` char(1) NOT NULL DEFAULT 'Y',
  `liveswithother` char(1) NOT NULL DEFAULT 'N',
  `otherguardian` varchar(250) DEFAULT NULL,
  `sex` char(1) NOT NULL DEFAULT 'M',
  `note` text,
  `archived` char(1) NOT NULL DEFAULT 'N',
  `priorityid` int(11) DEFAULT '0',
  `onlineregistration` varchar(1) NOT NULL DEFAULT 'N',
  `onlineregistrationaccept` varchar(1) NOT NULL DEFAULT 'N',
  `registrationconfirmed` varchar(1) NOT NULL DEFAULT 'N',
  `registrationconfirmeddate` datetime DEFAULT NULL,
  `createddate` datetime DEFAULT NULL,
  `modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `fullpart` varchar(1) DEFAULT 'F',
  `parttimedays` int(11) DEFAULT '10',
  `parttimedaystype` varchar(45) DEFAULT 'D',
  `parttimedaystypecode` varchar(45) DEFAULT 'MONTH',
  `program` varchar(45) DEFAULT 'daycare',
  `registrationnote` varchar(2000) DEFAULT NULL,
  `registrationnoteread` varchar(1) DEFAULT 'N',
  `registrationsubsidy` varchar(45) DEFAULT 'noplan',
  `registrationsubsidydate` datetime DEFAULT NULL,
  `registrationsubsidyamount` decimal(10,2) DEFAULT '0.00',
  PRIMARY KEY (`id`),
  KEY `Familyid` (`familyid`),
  KEY `companyid` (`companyid`),
  KEY `startdate` (`startdate`),
  KEY `enddate` (`enddate`),
  KEY `roomid` (`groupid`),
  KEY `providerisparent` (`providerisparent`)
) ENGINE=InnoDB AUTO_INCREMENT=93685 DEFAULT CHARSET=latin1;

СТОЛ СЕМЬИ

CREATE TABLE `families` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `accountnumber` varchar(100) DEFAULT NULL,
  `name` varchar(245) NOT NULL COMMENT 'The account name will typically be the name of the parent responsible for payment',
  `motherid` int(10) unsigned NOT NULL,
  `fatherid` int(10) unsigned NOT NULL,
  `balance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `notes` varchar(2000) DEFAULT NULL,
  `companyid` int(10) unsigned NOT NULL,
  `status` varchar(1) NOT NULL DEFAULT 'A',
  `financialaidrequired` char(1) NOT NULL DEFAULT 'N',
  `intakesurveyid` int(10) unsigned DEFAULT NULL,
  `referralid` int(10) unsigned NOT NULL DEFAULT '0',
  `registrationemailrequired` varchar(1) DEFAULT 'N',
  `registrationemailsent` varchar(1) DEFAULT 'N',
  `registrationemaildate` date DEFAULT NULL,
  `registrationemailaddressfound` varchar(1) DEFAULT NULL,
  `waitinglistemailrequired` varchar(1) DEFAULT 'N',
  `waitinglistemailsent` varchar(1) DEFAULT 'N',
  `waitinglistemaildate` date DEFAULT NULL,
  `waitinglistemailaddressfound` varchar(1) DEFAULT NULL,
  `activationemailrequired` varchar(1) DEFAULT 'N',
  `activationemailsent` varchar(1) DEFAULT 'N',
  `activationemaildate` date DEFAULT NULL,
  `activationemailaddressfound` varchar(1) DEFAULT NULL,
  `createddate` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `companyid` (`companyid`),
  KEY `intakesurveyid` (`intakesurveyid`),
  KEY `status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=9803007 DEFAULT CHARSET=latin1;

ТАБЛИЦА СДЕЛОК

CREATE TABLE `transactions` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `familyid` int(10) unsigned NOT NULL,
  `date` datetime NOT NULL,
  `transactiontype` varchar(1) NOT NULL DEFAULT 'C' COMMENT '''C'' = Charge, ''P'' = Payment',
  `paymenttype` varchar(3) DEFAULT NULL COMMENT '''DBT'' = Debit, ''CSH'' = Cash, ''CRE'' = Credit Card, ''CHQ'' = Cheque, ''MNY'' = Money Order,''EFT'' = Electronic Funds Transfer',
  `comment` varchar(500) DEFAULT NULL,
  `amount` decimal(10,2) NOT NULL DEFAULT '0.00',
  `reference` varchar(45) DEFAULT NULL,
  `chargeitem` int(10) unsigned DEFAULT '0',
  `taxdeductible` varchar(1) NOT NULL DEFAULT 'Y',
  `payer` varchar(1) DEFAULT 'M',
  `createddate` datetime DEFAULT NULL,
  `modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `Familyid` (`familyid`),
  KEY `Transaction Type` (`transactiontype`),
  KEY `Tax Deductible` (`taxdeductible`),
  KEY `date` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=1013472 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;

Ответы [ 4 ]

0 голосов
/ 20 мая 2018

Предполагая, что вы имеете в виду это (именно так MySQL будет интерпретировать это):

(this AND that ...) OR (f.id=...)

Давайте использовать UNION вместо OR.(OR плохо оптимизирует.)

Давайте также использовать 'стандартный' JOIN...ON вместо 'commajoin'.

Давайте не будем скрывать столбец внутри функции (YEAR);он запрещает использование индекса.

Вас уже ругали за то, что вы не сказали, какая таблица содержит status.Я вижу, что Хамун случайно потерял тот факт (?), Что status находится в f.Я предполагаю, что.

DISTINCT не является функцией, поэтому я удалил парены после нее.

Я выберу UNION DISTINCT (медленнее, но соответствует ORсемантика) вместо UNION ALL (быстрее, но может дублировать строку).

Я переместу children к внешнему SELECT, чтобы избежать некоторых возможных сбоев.

Когда GROUP BY и ORDER BY совпадают, запрос может выполняться быстрее.Итак, предполагая, что id и name логически связаны между собой, я думаю, что это даст вам одинаковую группировку и порядок:

GROUP BY name, id
ORDER BY name, id

Соединение всех моих советов:

SELECT  x.id, x.name,
        GROUP_CONCAT(DISTINCT c.firstname) children
    FROM (
           ( SELECT  f.id, f.name,
                FROM  families f
                JOIN  transactions t  ON f.id = t.familyid
                WHERE  f.companyid = 1170
                  AND  t.transactiontype = 'P'
                  AND  t.taxdeductible = 'Y'
                  AND  t.date >= '2017-01-01'
                  AND  t.date <  '2017-01-01' + INTERVAL 1 YEAR
                  AND  f.status = 'A'
           )
           UNION DISTINCT
           ( SELECT   f.id, f.name
                FROM  families f
                WHERE  f.id = 9779432
           ) 
         ) AS x
    JOIN  children c  ON x.id = c.familyid
    GROUP BY  x.name, x.id
    ORDER BY  x.name, x.id 

Вам понадобятся эти индексы.Порядок столбцов обычно важен.

f:  I assume it has PRIMARY KEY(id)
f:  (companyid, status)   -- in either order
t:  (familyid, transactiontype, taxdeductible, date)
t:  (transactiontype, taxdeductible, date, familyid)
c:  (familyid, firstname)

Некоторые примечания:

  • Я дал 2 индекса для t - поставьте оба, тем самым позволяя Оптимизатору решить, начинать ли сf или t.
  • Некоторые индексы «покрывают», что дает дополнительный импульс.
  • После переформулировки, DISTINCT в GROUP_CONCAT может оказаться ненужным.
  • Несколько индексов с одним столбцом часто не так же выгодны, как «составной» (с несколькими столбцами) индекс.
0 голосов
/ 14 мая 2018

Пожалуйста, предоставьте свою схему таблиц. Нам нужно проверить, какие у вас индексы.

Тем временем вы можете попробовать JOIN таблицы и удалить ORDER BY. Из того, что я вижу, у вас есть только один f.id = 9779432, так почему вы должны заказать то же значение?

Проверьте ваше OR состояние, я преобразовал его в нечто, что имеет смысл для меня. Ваше первоначальное утверждение с таким широким ИЛИ означает, что вам нужно что-то YEAR(t.date) OR f.id = 9779432 имеет ли это для вас какой-то смысл?

SELECT 
    f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
    families f
INNER JOIN children c
ON f.id = c.familyid
INNER JOIN transactions t
ON f.id = t.familyid
   AND t.transactiontype = 'P'
   AND t.taxdeductible = 'Y'
   AND YEAR(t.date) = 2017
WHERE
    (f.companyid = 1170 OR f.id = 9779432)
    AND f.status = 'A'

GROUP BY f.id;
0 голосов
/ 14 мая 2018

Лучше всего использовать синтаксис JOIN 21-го века.

SELECT f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
  FROM families f
  JOIN children c ON f.id = c.familyid
  JOIN transactions t ON f.id = t.familyid
 WHERE f.companyid = 1170 
   AND t.transactiontype = 'P'
   AND t.taxdeductible = 'Y'
   AND YEAR(t.date) = 2017
   AND status = 'A'
    OR f.id = 9779432
 GROUP BY f.id
 ORDER BY name;

Измените AND YEAR(t.date) = 2017 на AND t.date >='2017-01-01 AND t.date < '2018-01-01'. Зачем? Форма YEAR() этого предложения фильтра не sargeable .

В вашем вопросе нет способа определить, какая таблица содержит столбец status, и для производительности это очень важно . Если это t.status, попробуйте создать составной индекс для

 transaction(status, transactiontype, taxdeductible, date, familyid)

Затем попробуйте составной индекс на

 transaction(familyid, status, transactiontype, taxdeductible, date)

Один из них должен сильно помочь. Зачем? при выполнении запроса к таблице transaction MySQL может произвольно получить доступ к индексу к первой приемлемой записи: той, которая соответствует всем критериям = и имеет самое низкое значение date. Затем он может сканировать индекс последовательно, пока не найдет последнюю подходящую дату.

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

Если столбец status отсутствует в таблице transaction, уберите его из этого индекса.

0 голосов
/ 14 мая 2018

Попробуйте

EXPLAIN
SELECT 
    f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
    families f,
    children c,
    transactions t
WHERE
    f.companyid = 1170 AND f.id = t.familyid
        AND f.id = c.familyid
        AND t.transactiontype = 'P'
        AND t.taxdeductible = 'Y'
        AND YEAR(t.date) = 2017
        AND f.status = 'A'
        OR f.id = 9779432
GROUP BY f.id
ORDER BY name;

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

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

Также я бы рекомендовал никогда не использовать кратные from, но использовать оператор JOIN вместо того, чтобы иметь возможность нацеливать индексы объединенной таблицы и индекс против этого

...