Запрос SQLite выполняется в 10 раз медленнее, чем запрос MSAccess. - PullRequest
1 голос
/ 07 января 2010

У меня есть база данных MS Access на 800 МБ, которую я перенес на SQLite. Структура базы данных следующая (база данных SQLite после миграции составляет около 330 МБ):

Таблица Occurrence содержит 1 600 000 записей. Таблица выглядит так:

CREATE TABLE Occurrence 
(
SimulationID  INTEGER,    SimRunID   INTEGER,    OccurrenceID   INTEGER,
OccurrenceTypeID    INTEGER,    Period    INTEGER,    HasSucceeded    BOOL, 
PRIMARY KEY (SimulationID,  SimRunID,   OccurrenceID)
)

Имеет следующие индексы:

CREATE INDEX "Occurrence_HasSucceeded_idx" ON "Occurrence" ("HasSucceeded" ASC)

CREATE INDEX "Occurrence_OccurrenceID_idx" ON "Occurrence" ("OccurrenceID" ASC)

CREATE INDEX "Occurrence_SimRunID_idx" ON "Occurrence" ("SimRunID" ASC)

CREATE INDEX "Occurrence_SimulationID_idx" ON "Occurrence" ("SimulationID" ASC)

Таблица OccurrenceParticipant содержит 3 400 000 записей. Таблица выглядит так:

CREATE TABLE OccurrenceParticipant 
(
SimulationID    INTEGER,     SimRunID    INTEGER,    OccurrenceID     INTEGER,
RoleTypeID     INTEGER,     ParticipantID    INTEGER
)

Имеет следующие индексы:

CREATE INDEX "OccurrenceParticipant_OccurrenceID_idx" ON "OccurrenceParticipant" ("OccurrenceID" ASC)

CREATE INDEX "OccurrenceParticipant_ParticipantID_idx" ON "OccurrenceParticipant" ("ParticipantID" ASC)

CREATE INDEX "OccurrenceParticipant_RoleType_idx" ON "OccurrenceParticipant" ("RoleTypeID" ASC)

CREATE INDEX "OccurrenceParticipant_SimRunID_idx" ON "OccurrenceParticipant" ("SimRunID" ASC)

CREATE INDEX "OccurrenceParticipant_SimulationID_idx" ON "OccurrenceParticipant" ("SimulationID" ASC)

Таблица InitialParticipant содержит 130 записей. Структура таблицы

CREATE TABLE InitialParticipant 
(
ParticipantID    INTEGER  PRIMARY KEY,     ParticipantTypeID    INTEGER,
ParticipantGroupID     INTEGER
)

Таблица имеет следующие индексы:

CREATE INDEX "initialpart_participantTypeID_idx" ON "InitialParticipant" ("ParticipantGroupID" ASC)

CREATE INDEX "initialpart_ParticipantID_idx" ON "InitialParticipant" ("ParticipantID" ASC)

Таблица ParticipantGroup имеет 22 записи. Похоже

CREATE TABLE ParticipantGroup   (
ParticipantGroupID    INTEGER,    ParticipantGroupTypeID     INTEGER,
Description    varchar (50),      PRIMARY KEY(  ParticipantGroupID  )
)

Таблица имеет следующий индекс: CREATE INDEX «ParticipantGroup_ParticipantGroupID_idx» ON «ParticipantGroup» («ParticipantGroupID» ASC)

Таблица tmpSimArgs имеет 18 записей. Он имеет следующую структуру:

CREATE TABLE tmpSimArgs (SimulationID varchar, SimRunID int(10))

И следующие индексы:

CREATE INDEX tmpSimArgs_SimRunID_idx ON tmpSimArgs(SimRunID ASC)

CREATE INDEX tmpSimArgs_SimulationID_idx ON tmpSimArgs(SimulationID ASC)

Таблица «tmpPartArgs» содержит 80 записей. Имеет следующую структуру:

CREATE TABLE tmpPartArgs(participantID INT)

И приведенный ниже индекс:

CREATE INDEX tmpPartArgs_participantID_idx ON tmpPartArgs(participantID ASC)

У меня есть запрос, который включает несколько ВНУТРЕННИХ СОЕДИНЕНИЙ, и проблема, с которой я сталкиваюсь, заключается в том, что версия запроса Access занимает около секунды, тогда как версия того же запроса SQLite занимает 10 секунд (примерно в 10 раз медленнее!). Это невозможно для меня, чтобы перейти обратно на Access и SQLite мой единственный вариант.

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

Запрос в Access (весь запрос занимает 1 секунду):

SELECT ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period, Count(OccurrenceParticipant.ParticipantID) AS CountOfParticipantID FROM 
( 
   ParticipantGroup INNER JOIN InitialParticipant ON ParticipantGroup.ParticipantGroupID =  InitialParticipant.ParticipantGroupID
) INNER JOIN 
(
tmpPartArgs INNER JOIN 
  (
     (
        tmpSimArgs INNER JOIN Occurrence ON (tmpSimArgs.SimRunID = Occurrence.SimRunID)   AND (tmpSimArgs.SimulationID = Occurrence.SimulationID)
     ) INNER JOIN OccurrenceParticipant ON (Occurrence.OccurrenceID =    OccurrenceParticipant.OccurrenceID) AND (Occurrence.SimRunID = OccurrenceParticipant.SimRunID) AND (Occurrence.SimulationID = OccurrenceParticipant.SimulationID)
  ) ON tmpPartArgs.participantID = OccurrenceParticipant.ParticipantID
) ON InitialParticipant.ParticipantID = OccurrenceParticipant.ParticipantID WHERE (((OccurrenceParticipant.RoleTypeID)=52 Or (OccurrenceParticipant.RoleTypeID)=49)) AND Occurrence.HasSucceeded = True GROUP BY ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period;

Запрос SQLite выглядит следующим образом (этот запрос занимает около 10 секунд):

SELECT ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period, Count(ij2.occpParticipantID) AS CountOfParticipantID FROM 
(
   SELECT ip.ParticipantGroupID AS ipParticipantGroupID, ip.ParticipantID AS ipParticipantID, ip.ParticipantTypeID, pg.ParticipantGroupID AS pgParticipantGroupID, pg.ParticipantGroupTypeID, pg.Description FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID
) AS ij1 INNER JOIN 
(
   SELECT tpa.participantID AS tpaParticipantID, ij3.* FROM tmpPartArgs AS tpa INNER JOIN 
     (
       SELECT ij4.*, occp.SimulationID as occpSimulationID, occp.SimRunID AS occpSimRunID, occp.OccurrenceID AS occpOccurrenceID, occp.ParticipantID AS occpParticipantID, occp.RoleTypeID FROM 
          (
              SELECT tsa.SimulationID AS tsaSimulationID, tsa.SimRunID AS tsaSimRunID, occ.SimulationID AS occSimulationID, occ.SimRunID AS occSimRunID, occ.OccurrenceID AS occOccurrenceID, occ.OccurrenceTypeID, occ.Period, occ.HasSucceeded FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID)
          ) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID =      occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID)
    ) AS ij3 ON tpa.participantID = ij3.occpParticipantID
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1 GROUP BY ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period;   

Я не знаю, что я здесь делаю неправильно. У меня есть все индексы, но я думаю, что мне не хватает декларировать какой-то ключевой индекс, который поможет мне. Интересно, что перед миграцией мое «исследование» SQLite показало, что SQLite быстрее, меньше и лучше во всех аспектах, чем Access. Но я не могу заставить SQLite работать быстрее, чем Access с точки зрения запросов. Я повторяю, что я новичок в SQLite и, очевидно, у меня нет особых идей и опыта, поэтому, если какая-нибудь ученая душа сможет помочь мне с этим, это будет очень цениться.

Ответы [ 3 ]

2 голосов
/ 07 января 2010

Я переформатировал ваш код (используя мой домашний файл sql formatter ), чтобы, надеюсь, другим было легче читать ..

Переформатированный запрос:

SELECT
    ij1.Description,
    ij2.occSimulationID,
    ij2.occSimRunID,
    ij2.Period,
    Count(ij2.occpParticipantID) AS CountOfParticipantID

FROM (

    SELECT
        ip.ParticipantGroupID AS ipParticipantGroupID,
        ip.ParticipantID AS ipParticipantID,
        ip.ParticipantTypeID,
        pg.ParticipantGroupID AS pgParticipantGroupID,
        pg.ParticipantGroupTypeID,
        pg.Description

    FROM ParticipantGroup AS pg

    INNER JOIN InitialParticipant AS ip
            ON pg.ParticipantGroupID = ip.ParticipantGroupID

) AS ij1

INNER JOIN (

    SELECT
        tpa.participantID AS tpaParticipantID,
        ij3.*

    FROM tmpPartArgs AS tpa

    INNER JOIN (

        SELECT
            ij4.*,
            occp.SimulationID AS occpSimulationID,
            occp.SimRunID AS occpSimRunID,
            occp.OccurrenceID AS occpOccurrenceID,
            occp.ParticipantID AS occpParticipantID,
            occp.RoleTypeID

        FROM (

            SELECT
                tsa.SimulationID AS tsaSimulationID,
                tsa.SimRunID AS tsaSimRunID,
                occ.SimulationID AS occSimulationID,
                occ.SimRunID AS occSimRunID,
                occ.OccurrenceID AS occOccurrenceID,
                occ.OccurrenceTypeID,
                occ.Period,
                occ.HasSucceeded

            FROM tmpSimArgs AS tsa

            INNER JOIN Occurrence AS occ
                    ON (tsa.SimRunID = occ.SimRunID)
                   AND (tsa.SimulationID = occ.SimulationID)

        ) AS ij4

        INNER JOIN OccurrenceParticipant AS occp
                ON (occOccurrenceID = occpOccurrenceID)
               AND (occSimRunID = occpSimRunID)
               AND (occSimulationID = occpSimulationID)

    ) AS ij3
      ON tpa.participantID = ij3.occpParticipantID

) AS ij2
  ON ij1.ipParticipantID = ij2.occpParticipantID

WHERE (

    (

        (ij2.RoleTypeID) = 52
        OR
        (ij2.RoleTypeID) = 49

    )

)
  AND ij2.HasSucceeded = 1

GROUP BY
    ij1.Description,
    ij2.occSimulationID,
    ij2.occSimRunID,
    ij2.Period;

Что касается JohnFx (выше), меня смутили производные представления. Я думаю, что на самом деле в этом нет необходимости, тем более что все они являются внутренними соединениями. Итак, ниже я попытался уменьшить сложность. Пожалуйста, просмотрите и проверьте производительность. Мне пришлось сделать перекрестное соединение с tmpSimArgs, так как оно присоединяется только к Occurence - я предполагаю, что это желаемое поведение.

SELECT
    pg.Description,
    occ.SimulationID,
    occ.SimRunID,
    occ.Period,
    COUNT(occp.ParticipantID) AS CountOfParticipantID

FROM ParticipantGroup AS pg

INNER JOIN InitialParticipant AS ip
        ON pg.ParticipantGroupID = ip.ParticipantGroupID

CROSS JOIN tmpSimArgs AS tsa

INNER JOIN Occurrence AS occ
        ON tsa.SimRunID = occ.SimRunID
       AND tsa.SimulationID = occ.SimulationID

INNER JOIN OccurrenceParticipant AS occp
        ON occ.OccurrenceID = occp.OccurrenceID
       AND occ.SimRunID = occp.SimRunID
       AND occ.SimulationID = occp.SimulationID

INNER JOIN tmpPartArgs AS tpa
        ON tpa.participantID = occp.ParticipantID

WHERE occ.HasSucceeded = 1
  AND (occp.RoleTypeID = 52 OR occp.RoleTypeID = 49 )

GROUP BY
    pg.Description,
    occ.SimulationID,
    occ.SimRunID,
    occ.Period;
0 голосов
/ 07 января 2010

Я бы предложил переместить фильтрацию ij2.RoleTypeID из самого внешнего запроса в ij3, использовать IN вместо OR и переместить запрос HasSucceeded в ij4.

0 голосов
/ 07 января 2010

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

SELECT5 * FROM 
(
SELECT4 FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID
) AS ij1 INNER JOIN 
(
   SELECT3 * FROM tmpPartArgs AS tpa INNER JOIN 
      (
          SELECT2 * FROM 
              (
                  SELECT1 * FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID)
              ) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID =      occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID)
      ) AS ij3 ON tpa.participantID = ij3.occpParticipantID
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1

Приложение, над которым я работаю, является приложением Simulation, и для понимания контекста вышеупомянутого запроса я счел необходимым дать краткое объяснение приложения. Давайте предположим, что есть планета с некоторыми начальными ресурсами и живыми агентами. Планете разрешено существовать в течение 1000 лет, а действия, выполняемые агентами, отслеживаются и сохраняются в базе данных. Через 1000 лет планета разрушена и вновь воссоздана с тем же набором исходных ресурсов и живых агентов, что и в первый раз. Это (создание и уничтожение) повторяется 18 раз, и все действия агентов, выполненные за эти 1000 лет, сохраняются в базе данных. Таким образом, весь наш эксперимент состоит из 18 воссозданий, которые называются «Симуляция». Каждый из 18 раз воссоздания планеты называется пробегом, а каждый из 1000 лет пробега называется периодом. Таким образом, «Моделирование» состоит из 18 прогонов, и каждый прогон состоит из 1000 периодов. В начале каждого прогона мы присваиваем «Моделированию» начальный набор элементов знаний и динамических агентов, которые взаимодействуют друг с другом и элементами. Элемент знаний хранится агентом в хранилище знаний. Хранилище знаний также считается участвующим субъектом в нашем моделировании. Но эта концепция (в отношении хранилищ знаний) не важна. Я пытался быть детальным о каждом операторе SELECT и соответствующих таблицах.

SELECT1: я думаю, что этот запрос может быть заменен только таблицей «Вхождение», так как он ничего не делает. В таблице Occurrence хранятся различные действия, предпринятые агентами, в каждом периоде каждого прогона симуляции определенного «Симуляции». Обычно каждое «Моделирование» состоит из 18 прогонов. И каждый прогон состоит из 1000 периодов. Агенту разрешено выполнять действия в каждом периоде каждого прогона в «Моделировании». Но таблица «Вхождение» не хранит никаких сведений об агентах, которые выполняют действия. Таблица Occurrence может хранить данные, относящиеся к нескольким «Симуляциям».

SELECT2: этот запрос просто возвращает сведения о действиях, выполненных в каждом периоде каждого прогона «Моделирования», а также сведения обо всех участниках «Моделирования», таких как их соответствующие идентификаторы участника. В таблице OccurrenceParticipant хранятся записи для каждого участвующего объекта моделирования, в который входят агенты, хранилища знаний, элементы знаний и т. Д.

SELECT3: этот запрос возвращает только те записи из псевдотаблицы ij3, которые связаны с агентами и элементами знаний. Все записи в ij3, касающиеся элементов знаний, будут отфильтрованы.

SELECT4: этот запрос присоединяет поле «Описание» к каждой записи «InitialParticipant». Обратите внимание, что столбец «Описание» является столбцом «Вывод» всего запроса. Таблица InitialParticipant содержит запись для каждого агента и каждого элемента знаний, который изначально назначен «Моделированию»

SELECT5: этот последний запрос возвращает все записи из псевдотаблицы ij2, для которых RoleType участвующего объекта (который может быть либо агентом, либо элементом знаний) равен 49 или 52.

...