如何设计可扩展的集合操作?

时间:2016-05-23 04:13:09

标签: sql performance database-design scalability

这是教育应用程序向语言学习者教授单词的要求:

  1. 学习者可以声明他/她知道一个给定的单词
  2. 学习者可以记住给定的单词(稍后由应用程序管理)
  3. 学习者可以选择忽略给定的单词,以便以后不会给予他/她
  4. 管理员可以忽略一般的字词,以便不会被授予任何学习者
  5. 数据库中存在单词列表,作为参考
  6. 给学生的下一个词应该对他/她来说是新的
  7. 这是我们的设计:

    Words
        - Id
        - Word
        - Meaning
        - PartOfSpeech
    
    LearnerWords
        - Id
        - LearnerId
        - WordId
        - TypeOfInteraction (ignored, memorized, memorizing, reviewing, etc.)
    
    Learners
        - Id
        - Name
    

    这是我们执行的伪查询,用于查找给定学习者的下一个新单词:

    select top 1 * 
    from Words 
    where Id not in 
    (
        select WordId 
        from LearnerWords
        where LearnerId = @learnerId
    )
    

    当然,整体设计和查询要复杂得多,并且在为给定的学习者选择下一个新单词时会有更多细节。

    现在,假设单词列表包含100K个单词,并且学习者已经学习了超过5K个单词。使用给定的查询,越多学习者学习更多单词,这会越来越慢,越来越慢。

    这些类型的业务需求是否有更好的设计?在这种情况下如何设计规模?

3 个答案:

答案 0 :(得分:1)

对于您的LearnerWords表,我建议您将TypeOfInteraction列替换为一组可以为空的日期列,这些列指示每次互动何时发生或开始(如果没有,则为null),例如:

CREATE TABLE `LearnerWords` (
    `LearnerId`  int(11) UNSIGNED NOT NULL ,
    `WordId`  int(11) UNSIGNED NOT NULL ,
    `Ignored`  datetime NULL ,
    `Memorized`  datetime NULL ,
    `Memorizing`  datetime NULL ,
    `Reviewing`  datetime NULL ,
    PRIMARY KEY (`LearnerId`, `WordId`)
);

这会将您的表从每个学习者/单词/互动的行减少到每个学习者/单词一行,同时记录更多信息。

关于您的查询:

小心NOT IN和子查询(或任何动态值集)。 NOT IN如果检查的集合为空或仅包含NULL,则会返回NULL,这意味着对于LearnerWords中没有任何记录的学员,您的查询不会收到任何结果。

更好的方法是使用LEFT JOINNOT EXISTS,例如:

SELECT TOP 1 *
FROM Words w
WHERE NOT EXISTS (
    SELECT 1
    FROM LearnerWords lw
    WHERE lw.LearnerId = @learnerId
      AND lw.WordId = w.WordId
)

另请注意dcieslak的答案 - 索引对性能至关重要。

答案 1 :(得分:0)

只需在LearnerID上的LearnerWords中创建索引(加上复合索引中的TypeOfInteraction),它应该很快。您的数据库查询优化引擎将确定您只需要1行,它将使用索引非常快速地访问所需数据。

答案 2 :(得分:0)

答案取决于实际使用的DBMS。其中一些将以一种方式优化查询,一些方式 - 另一方式。大多数情况下,通过使用反连接模式,您不会失去执行速度:

select top 1 w.*
from Words w
left join LearnerWords lw on lw.LearnerId = @learnerId and lw.WordId = w.Id
where lw.WordId is null