有没有办法用if改进这个查询?

时间:2014-04-22 12:53:50

标签: mysql sql database-performance

我使用此查询从包含多种语言字符串的数据库中选择语言字符串。数据库如下所示:

`string_id`   BIGINT
`language_id` BIGINT
`datetime`    DATETIME
`text`        TEXT

例如,数据可能如下所示:

`string_id` | `language_id` | `datetime`          | `text`
1           | 1             | 2014.04.22 14:43:00 | hello world
1           | 2             | 2014.04.22 14:43:02 | hallo welt

所以这是德语和英语中的相同字符串。德语版改为英文版后两秒钟。

我使用此( sub )查询来获取加工字符串。如果请求的语言不存在,它会自动回退到任何语言。因此,例如,如果我要求西班牙语中的字符串(= id 3),此查询将回退到英语或德语:

SELECT
    z.`text`
FROM
    `language_strings` AS z
WHERE
    a.`joined_string_id` = z.`string_id` 
ORDER BY
    IF(z.`language_id` = 3, 1, 0) DESC,
    z.`datetime` DESC
LIMIT
    1

这里的性能问题是IF(..., 1, 0)删除了很多机会,因为每次执行查询时都必须计算结果。

我尝试了很多来改进这个查询,仍然创建了所有有用的索引。 MySQL能够使用内部缓存命中此查询,但没有缓存需要一些时间来计算。当获取大量行(例如1000)时,这是一个性能问题,因为MySQL必须执行1000个子序列。

您是否知道如何改进此查询?添加新列来存储预先计算的数据对我来说是一个选择。

4 个答案:

答案 0 :(得分:1)

(SELECT
    1 as ord, z.`text`
FROM
    `language_strings` AS z
WHERE
    a.`joined_string_id` = z.`string_id` and z.`language_id` = 3
limit 1)
union all
(SELECT
    2 as ord, z.`text`
FROM
    `language_strings` AS z
WHERE
    a.`joined_string_id` = z.`string_id`
ORDER BY
    z.`datetime` DESC
LIMIT 1)
ORDER BY ord
LIMIT 1

更新。 Twinkles谢谢你的说明。

答案 1 :(得分:1)

SELECT COALESCE(primary.`text`,fallback.`text`)
FROM (
  SELECT 1 `ord`, z.`text`, z.`datetime`
  FROM `language_strings` AS z
  WHERE z.`language_id` = 3
) primary
FULL OUTER JOIN
(
  SELECT 2 `ord`, z.`text`, z.`datetime`
  FROM `language_strings` AS z
) fallback
ON (primary.`string_id` = fallback.`string_id`
    AND primary.`string_id` = a.`joined_string_id`)
ORDER BY `ord` ASC, `datetime` DESC
LIMIT 1

答案 2 :(得分:1)

这似乎是一个相关的子查询,假设表a上有相当多的行,效率非常低。可能最好将其重新编码为已加入的子查询。

可能如下: -

SELECT a.*, IFNULL(ls1.`text`, ls2.`text`)
FROM some_table a
LEFT OUTER JOIN 
(
    SELECT string_id, MAX(datetime) AS MaxDateTime
    FROM language_strings
    WHERE language_id = 3
    GROUP BY string_id
) AS MainLanguage1
ON a.joined_string_id = MainLanguage1.string_id
LEFT OUTER JOIN language_strings ls1
ON MainLanguage1.string_id = ls1.string_id AND MainLanguage1.datetime = ls1.MaxDateTime
LEFT OUTER JOIN 
(
    SELECT string_id, MAX(datetime)
    FROM language_strings
    WHERE language_id != 3
    GROUP BY string_id
) AS MainLanguage2
ON a.joined_string_id = MainLanguage2.string_id
LEFT OUTER JOIN language_strings ls2
ON MainLanguage2.string_id = ls2.string_id AND MainLanguage2.datetime = ls2.MaxDateTime

这将获取string_id的最新日期,其中语言为3,然后是连接以获取匹配的文本,以及aa string_id的最新日期,其中语言不是3,然后是连接以获取与之匹配的文本。

然后返回的文本只是使用IFNULL带回来恢复语言3的文本,如果没有找到,则返回3以外语言的文本。

答案 3 :(得分:0)

虽然我测试了所有已发布的解决方案并且对它们的复杂性感到头疼,但我认为必须有更好的方法来实现这一点。来自@Twinkles的COALESCE的启发,在我决定尝试使用另一个代码,#34;临时"之前我知道了。绝对包含所有可能解决方案的表格。

这个小小的查询会生成该表,并保证每种语言都有一个条目:

INSERT INTO
    `language_strings_compiled`
(
    `string_id`,
    `language_id`,
    `text`
)
SELECT
    a.`string_id`,
    b.`language_id`,
    (
        SELECT
            z.`text`
        FROM
            `language_strings` AS z
        WHERE
            a.`string_id` = z.`string_id`
        ORDER BY
            IF(z.`language_id` = b.`language_id`, 1, 0) DESC,
            z.`datetime` DESC
        LIMIT 1
    ) AS `text`
FROM
    `language_strings` AS a
JOIN
    `languages` AS b
GROUP BY
    a.`string_id`,
    b.`language_id`

然后,我的子查询可能如下所示:

COALESCE
(
    (
        SELECT
            z.`text`
        FROM
            `language_strings_compiled` AS z
        WHERE
            a.`joined_string_id` = z.`string_id`
        AND
            z.`language_id` = 3
        LIMIT
            1
    ),
    (
        SELECT
            z.`text`
        FROM
            `language_strings` AS z
        WHERE
            a.`joined_string_id` = z.`string_id`
        ORDER BY
            IF(z.`language_id` = 3, 1, 0) DESC,
            z.`datetime` DESC
        LIMIT
            1
    )
)

此解决方案比没有"编译"的解决方案快10倍。表。它能够回归到旧的"解决方案,如果有一些新的语言字符串根本不为编译表所知。

感谢所有的解决方案,我尝试了所有这些,但每次我遇到"子子查询" -problem到目前为止。