Проблема производительности LINQ - PullRequest
0 голосов
/ 20 сентября 2010

У меня есть сайт Joomla, который использует JomSocial. У меня есть веб-приложение .NET, над которым я работаю, которое в конечном итоге заменит Joomla, так как я предпочитаю .NET над PHP. Сейчас у меня есть мобильный сайт .NET, которым пользуются пользователи.

LINQ to Entity сделал разработку очень быстрой, но сейчас я пытаюсь исправить проблемы с производительностью. Отправка сообщений друг другу - это занятие № 1, и на данный момент отправлено более 40 тыс. Сообщений. Это также, где у меня есть проблема с производительностью. Ниже приведены две таблицы, которые JomSocial использует для хранения сообщений. Ниже приведен мой текущий LINQ-код, который возвращает желаемые результаты, на это уходит всего две секунды.

Я думаю, что по именам столбцов вы, вероятно, сможете выяснить, как выглядят данные, но если нет, то я могу создать их, а затем опубликовать их здесь в нескольких, так как мне нужно немного закончить. Я должен упомянуть, что я использую Entity Framework с .NET 3.5 и MySQL с MySQL .NET Connector.

Таблица:

delimiter $$
CREATE TABLE `jos_community_msg` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `from` int(10) unsigned NOT NULL,
  `parent` int(10) unsigned NOT NULL,
  `deleted` tinyint(3) unsigned DEFAULT '0',
  `from_name` varchar(45) NOT NULL,
  `posted_on` datetime DEFAULT NULL,
  `subject` tinytext NOT NULL,
  `body` text NOT NULL,
  PRIMARY KEY (`id`),
  KEY `parent` (`parent`),
  KEY `deleted` (`deleted`),
  KEY `from` (`from`)
) ENGINE=MyISAM AUTO_INCREMENT=340 DEFAULT CHARSET=utf8$$

delimiter $$
CREATE TABLE `jos_community_msg_recepient` (
  `msg_id` int(10) unsigned NOT NULL,
  `msg_parent` int(10) unsigned NOT NULL DEFAULT '0',
  `msg_from` int(10) unsigned NOT NULL,
  `to` int(10) unsigned NOT NULL,
  `bcc` tinyint(3) unsigned DEFAULT '0',
  `is_read` tinyint(3) unsigned DEFAULT '0',
  `deleted` tinyint(3) unsigned DEFAULT '0',
  UNIQUE KEY `un` (`msg_id`,`to`),
  KEY `msg_id` (`msg_id`),
  KEY `to` (`to`),
  KEY `idx_isread_to_deleted` (`is_read`,`to`,`deleted`),
  KEY `from` (`msg_from`),
  KEY `parent` (`msg_parent`),
  KEY `deleted` (`deleted`),
  KEY `to_deleted` (`deleted`,`to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8$$

LINQ:

var messages = (
    from b in context.jos_community_msg
    join i in (
        from i in context.jos_community_msg_recepient
        join a in context.jos_community_msg on i.msg_id equals a.id
        where i.to == userId && && a.deleted == 0
        group a by a.parent into g
        select g.Max(p => p.id)) on b.id equals i
    join a in context.jos_community_msg_recepient on i equals a.msg_id
    orderby b.id descending
    select new MessageHeaderItem()
    {
        IsDeleted = false,
        IsRead = (a.is_read.Value == 0) ? false : true,
        MessageId = b.parent,
        Sent = b.posted_on.Value,
        Subject = b.subject,
        UserId = a.msg_from
    });

    total = messages.Count();
    return messages.Skip(start).Take(max).ToList();

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

Обновление: Вот генерируемый SQL:

SELECT
`Limit1`.`C1`, 
`Limit1`.`C2`, 
`Limit1`.`C3`, 
`Limit1`.`parent`, 
`Limit1`.`posted_on`, 
`Limit1`.`subject`, 
`Limit1`.`msg_from`, 
`Limit1`.`C4`, 
`Limit1`.`C5`, 
`Limit1`.`C6`
FROM (SELECT
`Extent1`.`id`, 
`Extent1`.`parent`, 
`Extent1`.`posted_on`, 
`Extent1`.`subject`, 
`Extent6`.`msg_from`, 
1 AS `C1`, 
cast(0 as decimal(0,0)) AS `C2`, 
CASE WHEN (0 = (`Extent6`.`is_read`)) THEN (cast(0 as decimal(0,0)))  ELSE (cast(1 as decimal(0,0))) END AS `C3`, 
'Test' AS `C4`, 
'' AS `C5`, 
'' AS `C6`
FROM `jos_community_msg` AS `Extent1` INNER JOIN (SELECT
(SELECT
Max(`Extent5`.`id`) AS `A1`
FROM (SELECT
      `jos_community_msg_recepient`.`bcc`, 
      `jos_community_msg_recepient`.`deleted`, 
      `jos_community_msg_recepient`.`is_read`, 
      `jos_community_msg_recepient`.`msg_from`, 
      `jos_community_msg_recepient`.`msg_id`, 
      `jos_community_msg_recepient`.`msg_parent`, 
      `jos_community_msg_recepient`.`to`
      FROM `jos_community_msg_recepient` AS `jos_community_msg_recepient`) AS `Extent4` INNER JOIN `jos_community_msg` AS `Extent5` ON (`Extent4`.`msg_id` = `Extent5`.`id`) OR ((`Extent4`.`msg_id` IS  NULL) AND (`Extent5`.`id` IS  NULL))
 WHERE ((`Extent4`.`to` = 62) AND (0 = (`Extent5`.`deleted`))) AND ((`Extent5`.`parent` = `Project2`.`parent`) OR ((`Extent5`.`parent` IS  NULL) AND (`Project2`.`parent` IS  NULL)))) AS `C1`
FROM (SELECT
62 AS `p__linq__5`, 
`Distinct1`.`parent`
FROM (SELECT DISTINCT 
`Extent3`.`parent`
FROM (SELECT
      `jos_community_msg_recepient`.`bcc`, 
      `jos_community_msg_recepient`.`deleted`, 
      `jos_community_msg_recepient`.`is_read`, 
      `jos_community_msg_recepient`.`msg_from`, 
      `jos_community_msg_recepient`.`msg_id`, 
      `jos_community_msg_recepient`.`msg_parent`, 
      `jos_community_msg_recepient`.`to`
      FROM `jos_community_msg_recepient` AS `jos_community_msg_recepient`) AS `Extent2` INNER JOIN `jos_community_msg` AS `Extent3` ON (`Extent2`.`msg_id` = `Extent3`.`id`) OR ((`Extent2`.`msg_id` IS  NULL) AND (`Extent3`.`id` IS  NULL))
 WHERE (`Extent2`.`to` = 62) AND (0 = (`Extent3`.`deleted`))) AS `Distinct1`) AS `Project2`) AS `Project3` ON (`Extent1`.`id` = `Project3`.`C1`) OR ((`Extent1`.`id` IS  NULL) AND (`Project3`.`C1` IS  NULL)) INNER JOIN (SELECT
      `jos_community_msg_recepient`.`bcc`, 
      `jos_community_msg_recepient`.`deleted`, 
      `jos_community_msg_recepient`.`is_read`, 
      `jos_community_msg_recepient`.`msg_from`, 
      `jos_community_msg_recepient`.`msg_id`, 
      `jos_community_msg_recepient`.`msg_parent`, 
      `jos_community_msg_recepient`.`to`
      FROM `jos_community_msg_recepient` AS `jos_community_msg_recepient`) AS `Extent6` ON (`Project3`.`C1` = `Extent6`.`msg_id`) OR ((`Project3`.`C1` IS  NULL) AND (`Extent6`.`msg_id` IS  NULL))
 ORDER BY 
`id` DESC LIMIT 0,16) AS `Limit1`;

Вот объяснение из MySQL:

1   PRIMARY <derived2>  ALL                 16  
    2   DERIVED <derived3>  ALL                 55  Using temporary; Using filesort
    2   DERIVED Extent1 eq_ref  PRIMARY PRIMARY 4   Project3.C1 1   Using where
    2   DERIVED <derived9>  ALL                 333 Using where; Using join buffer
    9   DERIVED jos_community_msg_recepient ALL                 333 
    3   DERIVED <derived6>  ALL                 55  
    6   DERIVED <derived7>  ALL                 55  
    7   DERIVED <derived8>  ALL                 333 Using where; Using temporary
    7   DERIVED Extent3 eq_ref  PRIMARY,deleted PRIMARY 4   Extent2.msg_id  1   Using where; Distinct
    8   DERIVED jos_community_msg_recepient ALL                 333 
    4   DEPENDENT SUBQUERY  Extent5 ref PRIMARY,parent,deleted  parent  4   Project2.parent 2   Using where
    4   DEPENDENT SUBQUERY  <derived5>  ALL                 333 Using where; Using join buffer
    5   DERIVED jos_community_msg_recepient ALL                 333 

1 Ответ

0 голосов
/ 20 сентября 2010

Ваш запрос LINQ выглядит довольно хорошо. Вы используете проекцию на DTO (MessageHeaderItem), которая позволяет LINQ to Entities создавать очень оптимальный запрос. Однако вы должны использовать профилировщик SQL для проверки фактического запроса SQL, который выполняется. Возможно, LINQ to Entities запускает много запросов под прикрытием. Также возможно, что вам нужна некоторая настройка индекса. Скопируйте выполненный запрос из профилировщика SQL в мастер настройки SQL (входит в состав SQL Management Studio) и посмотрите, какие советы он даст.

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