SQL:数据操作和索引

时间:2012-06-19 23:10:39

标签: mysql database-design indexing

最近我一直在努力优化我的桌子,主要是因为我通过我学校的一些课程学到了很多关于数据库设计的知识。我也选择这样做,因为我在一些查询上得到了很多超时,最近发现这确实是我糟糕的数据库设计。

基本上,我将在此表上执行SELECT,UPDATE,INSERT和DELETE。

这是我当前的数据库架构:

-- ----------------------------
-- Table structure for `characters_items`
-- ----------------------------
DROP TABLE IF EXISTS `characters_items`;
CREATE TABLE `characters_items` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `master_id` int(10) unsigned NOT NULL DEFAULT '0',
  `item_id` smallint(6) NOT NULL,
  `amount` int(11) NOT NULL,
  `slot_id` smallint(9) NOT NULL DEFAULT '0',
  `type` tinyint(4) NOT NULL DEFAULT '0',
  `extra_data` text,
  PRIMARY KEY (`id`),
  KEY `master_id` (`master_id`),
  CONSTRAINT `characters_items_ibfk_1` FOREIGN KEY (`master_id`) REFERENCES `characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=904 DEFAULT CHARSET=latin1;

在我的程序中,我将操作大量(一次最多500行,因为你可以看到这是一个包含所有字符项的表)。

我还了解到,如果您正在进行数据操作,索引值会降低查询速度。

以下是我将要使用的一些查询:

            StringBuilder query = new StringBuilder();

            client.ClearParameters();
            client.AddParameter("master_id", this.owner.MasterId);
            client.AddParameter("type", (byte)CharacterItemType.Bank);
            client.AddParameter("capacity", this.Capacity);

            // Grab the original items.
            DataRow[] data = client.ReadDataTable("SELECT item_id,amount,slot_id FROM characters_items WHERE master_id=@master_id AND type=@type LIMIT @capacity").Select();
            Item[] originalItems = new Item[this.Capacity];
            if (data != null && data.Length > 0)
            {
                for (short i = 0; i < data.Length; i++)
                {
                    DataRow row = data[i];

                    short id = (short)row[0];
                    int count = (int)row[1];
                    short slotId = (short)row[2];

                    originalItems[slotId] = new Item(id, count);
                }
            }

            // Now we compare the items to see if anything has been changed.
            Item[] items = this.ToArray();
            for (short i = 0; i < items.Length; i++)
            {
                Item item = items[i];
                Item original = originalItems[i];

                // item was added.
                if (item != null && original == null)
                {
                    query.Append("INSERT INTO characters_items (master_id,item_id,amount,slot_id,type,extra_data) ");
                    query.Append("VALUES (");
                    query.Append(this.owner.MasterId);
                    query.Append(",");
                    query.Append(item.Id);
                    query.Append(",");
                    query.Append(item.Count);
                    query.Append(",");
                    query.Append(i);
                    query.Append(",");
                    query.Append((byte)CharacterItemType.Bank);

                    string extraData = item.SerializeExtraData();
                    if (extraData != null)
                    {
                        query.Append(",'");
                        query.Append(extraData);
                        query.Append("'");
                    }
                    else
                    {
                        query.Append(",null");
                    }

                    query.Append(");");
                }
                // item was deleted.
                else if (item == null && original != null)
                {
                    query.Append("DELETE FROM characters_items WHERE slot_id=");
                    query.Append(i);
                    query.Append(" AND master_id=");
                    query.Append(this.owner.MasterId);
                    query.Append(" AND type=");
                    query.Append((byte)CharacterItemType.Inventory);
                    query.Append(" LIMIT 1;");
                }
                // item was modified.
                else if (item != null && original != null)
                {
                    if (item.Id != original.Id || item.Count != original.Count)
                    {
                        query.Append("UPDATE characters_items SET item_id=");
                        query.Append(item.Id);
                        query.Append(",amount=");
                        query.Append(item.Count);

                        string extraData = item.SerializeExtraData();
                        if (extraData != null)
                        {
                            query.Append(",extra_data='");
                            query.Append(extraData);
                            query.Append("'");
                        }
                        else
                        {
                            query.Append(",extra_data=null");
                        }

                        query.Append(" WHERE master_id=@master_id AND type=@type AND slot_id=");
                        query.Append(i);
                        query.Append(";");
                    }
                }
            }

            // If a query was actually built, we will execute it.
            if (query.Length > 0)
            {
                client.SetConnectionTimeout(60);
                client.ExecuteUpdate(query.ToString());
                return true;
            }
        }
        catch (Exception ex)
        {
            Program.Logger.PrintException(ex);
        }
        return false;

如您所见,我几乎总是引用slot_id,type和master_id字段。我想知道我是否将slot_id和类型字段设为索引字段,它将如何影响我的整体数据操作性能?会以积极的方式受到影响,还是会以负面的方式受到影响?

请给我一些建议(除了C#代码,我稍后会修复它!)

3 个答案:

答案 0 :(得分:3)

首先,当您可以使用绑定参数时,从不动态构造SQL文本。绑定参数可以保护您免受SQL injection的影响,并允许DBMS一次prepare SQL语句并重复使用多次,从而提高性能。

至于索引......它们通常是寻找和修改数据之间的权衡 - 它们加速前者 1 并减慢后者。 但是,如果数据的修改方式也包含搜索 2 ,那么索引实际上也可以最终加快修改速度。

索引应始终根据您的应用程序正在进行的实际查询量身定制,在您的情况下包括以下内容:

  • SELECT ... WHERE master_id=... AND type=...
  • INSERT ...
  • DELETE ... WHERE slot_id=... AND master_id=... AND type=...
  • UPDATE ... WHERE master_id=... AND type=... AND slot_id=...

所有3个WHERE子句可以通过{master_id, type, slot_id}上的单个复合索引有效地“提供”。只有INSERT语句(本质上没有WHERE)才会被这个附加索引所伤害。

考虑:

  • 如果复合索引的某些字段的选择性较低,您可以考虑从索引中删除它们以使其更小并且更易于缓存。例如,如果预计共享相同master_id的行数较少,则master_id上的索引不会显着影响搜索性能,但会使索引更小,更容易/更快地维护。
  • 另一方面,您还可以考虑在索引中包含WHERE子句不直接使用但在查询中其他位置列出的字段。例如,SELECT item_id, amount, slot_id item_id,我们可以将amountslot_id添加到索引的“后端”(DELETE ... WHERE ...已经在索引)。
  • MySQL / InnoDB 总是聚类表,因此二级索引的价格相对较高(参见cover中的“聚类的缺点”)。

正如您所看到的,所有这些都是一个非常精细的平衡行为,甚至专家也无法始终预测最佳平衡。因此,如果有疑问,请在决定之前衡量

关于索引和数据库性能的一般性介绍,我热烈推荐:this article


1 假设它们被正确使用。

2 通常:UPDATE ... WHERE ...和{{1}}。

答案 1 :(得分:1)

为了获得给定UPDATE和DELETE语句的最佳性能,我建议:

ALTER TABLE characters_items
ADD KEY characters_items_IX1 (master_id, item_id, slot_id);

为了获得SELECT语句以及DML语句的最佳性能,可以修改索引以包含两个附加列:

ALTER TABLE characters_items
ADD KEY characters_items_IX1 (master_id, item_id, slot_id, type, amount);

(注意:你只需添加其中一个索引,你不需要两个。)


我们观察到你的UPDATE和DELETE语句在所有三列上指定了equals谓词。在这种情况下,您希望索引中的列从最高基数排序到最低。 (即,具有大量不同值的列,首先是最高选择性,然后是其他列。)

对于表中的大量行,这样的索引很可能会提高UPDATE和DELETE操作的性能。

(鉴于表中的auto_increment值只有904,这意味着表中的行可能少于一千行,因此您不太可能看到任何性能差异。)

如果master_id已经“几乎是唯一的”,那么该列上的现有索引就足够了。

如果添加我推荐的索引,那么现有索引是多余的,可以删除。 (使用现有索引的任何查询都可能使用新索引,master_id作为前导列。)

是的,指数设计存在权衡。在执行DML操作时,还需要执行其他工作来维护索引。

您不希望仅在slot_id或item_id上添加索引,如果它们不是选择性的,或者您没有任何可以使用它们的查询。拥有未使用的索引是没有意义的。

就其他索引而言,这实际上取决于您正在执行的其他语句,特别是SELECT语句。我们真的想查看谓词(WHERE子句和JOIN条件),看看其他索引是否有帮助。


附录:

问:单独添加密钥和作为一个组添加密钥有什么区别? (就像你给出的例子)

在这种情况下,三个单独的索引(在master_id,item_id和slot_id上)将没有用,因为执行计划可能只使用其中一个,理想情况下,具有最高选择性的索引。 “组合索引”的执行计划可能比全表扫描更快,但它们很少胜过已包含其中所有列的单个索引。

最大的区别在于索引中的“领先”列。如果索引的前导列上没有谓词(WHERE子句)或ORDER BY,则不太可能使用索引。


SELECT语句的最佳索引是“覆盖”索引,即包含查询中引用的所有列的索引,这样可以从索引中满足查询,而不必引用页面在数据表中。

ADD KEY characters_items_IX1 (master_id, item_id, slot_id, type, amount);

答案 2 :(得分:0)

如果selectxes对select语句的where子句具有高选择性,那么它将加速你的选择。

检查最常运行的select语句,然后在where子句中使用的字段编制索引。 如果你使用通配符(特别是像'%something%'这样的东西),那么索引就没用了。

我不记得MySQL是否可以为索引包含列,但如果是这样,您可以通过添加select语句中的列而不是where子句中的列作为索引中包含的列来获得额外的好处。

否则,将要执行的实际操作是索引查找,然后是键查找。索引始终将关联数据行的主键作为包含列,因此一旦找到索引键,就会通过其主键查找该行。如果您可以避免密钥查找,则可以显着降低查询的IO成本。 当然,这会略微增加插入和更新的成本,并且会大大增加数据库占用的空间。