0

最近、私は自分のテーブルを最適化しようとしています。これは主に、学校のいくつかのコースを通じてデータベース設計について多くのことを学んだためです。また、一部のクエリで多くのタイムアウトが発生していたため、これを行うことを選択しましたが、最近、それが実際に私の悪いデータベース設計であることがわかりました。

基本的に、このテーブルで 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 フィールドと type フィールドをインデックス付きフィールドにすると、全体的なデータ操作のパフォーマンスにどのような影響があるのでしょうか? プラスの影響を受けるか、マイナスの影響を受けるか。

アドバイスをお願いします (C# コードを除いて、後で修正します!)

4

3 に答える 3

3

まず、バインドされたパラメータを代わりに使用できる場合は、SQLテキストを動的に作成しないでください。パラメータをバインドすると、SQLインジェクションから保護され、DBMSがSQLステートメントを1回準備して何度も再利用できるようにすることで、パフォーマンスを向上させることができます。

インデックスに関しては...一般的に、データの検索と変更の間のトレードオフです。前者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_idamountslot_id
  • MySQL / InnoDBは常にテーブルをクラスター化するため、セカンダリインデックスの価格は比較的高くなります(この記事の「クラスター化のデメリット」を参照)。

ご覧のとおり、これはすべてかなり手の込んだバランスを取る行為であり、専門家でさえ常に最適なバランスを予測できるとは限りません。したがって、疑わしい場合は、決定する前に測定してください。

インデックス作成のトピック、および一般的なデータベースパフォーマンスに関する優れた紹介については、次のことを強くお勧めします。インデックスを使用する。ルーク!


1それらが正しく使用されていると仮定します。

2通常:DELETE ... WHERE ...およびUPDATE ... WHERE ...

于 2012-06-20T01:32:05.890 に答える
1

特定の UPDATE ステートメントと DELETE ステートメントの最適なパフォーマンスを得るには、次のことをお勧めします。

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

SELECT ステートメントと DML ステートメントのパフォーマンスを最適化するために、インデックスを変更して 2 つの追加の列を含めることができます。

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

(注: これらのインデックスの 1 つだけを追加します。両方は必要ありません。)


UPDATE ステートメントと DELETE ステートメントで、3 つの列すべてに equals 述語が指定されていることがわかります。この場合、インデックス内の列をカーディナリティの高いものから低いものの順に並べる必要があります。(つまり、個別の値の数が最も多い列で、最初に選択性が最も高く、その後に他の列が続きます。)

テーブル内の行数が多い場合、このようなインデックスを使用すると、UPDATE および DELETE 操作のパフォーマンスが向上する可能性が非常に高くなります。

(テーブルの auto_increment 値がわずか 904 であることを考えると、パフォーマンスの違いが見られる可能性はほとんどありません。これは、おそらくテーブル内の行数が 1,000 未満であることを意味します。)

master_id がすでに「ほぼ一意」である場合は、その列の既存のインデックスで十分です。

私が推奨したインデックスを追加すると、既存のインデックスは冗長になり、削除される可能性があります。(既存のインデックスを使用するすべてのクエリは、master_id を先頭の列として、新しいインデックスを使用する可能性があります。)

はい、インデックスの設計にはトレードオフがあります。DML 操作が実行されるときに、インデックスを維持するために実行する追加の作業があります。

選択的でない場合、またはそれらを使用するクエリがない場合は、slot_id または item_id だけにインデックスを追加したくないでしょう。使用されていないインデックスを使用しても意味がありません。

他のインデックスに関しては、実行している他のステートメント、特に SELECT ステートメントに大きく依存します。追加のインデックスが役立つかどうかを確認するために、述語 (WHERE 句と JOIN 条件) を調べたいと思います。


補遺:

Q: キーを個別に追加する場合とグループとして追加する場合の違いは何ですか? (あなたが与えた例のように)

この場合、3 つの個別のインデックス (master_id、item_id、slot_id のインデックス) は役に立ちません。これは、実行プランがそのうちの 1 つ (理想的には、選択性が最も高いインデックス) のみを使用する可能性が高いためです。「インデックスを結合する」実行計画は、全テーブル スキャンよりも高速になる可能性がありますが、すべての列が既に含まれている単一のインデックスよりもパフォーマンスが優れていることはめったにありません。

大きな違いは、インデックスの「先頭」列です。インデックスの先頭列に述語 (WHERE 句) または ORDER BY がない場合、インデックスが使用される可能性は低くなります。


SELECT ステートメントに最適なインデックスは、「カバリング」インデックス、つまり、クエリで参照されるすべての列を含むインデックスであり、データ内のページを参照する必要なく、インデックスからクエリを満たすことができます。テーブル。

ADD KEY characters_items_IX1 (master_id, item_id, slot_id, type, amount);
于 2012-06-19T23:19:31.713 に答える
0

Idex は、select ステートメントの where 句の選択性が高い場合、select を高速化します。

最も頻繁に実行している select ステートメントを確認し、where 句で使用するフィールドにインデックスを付けます。ワイルドカード (特に '%something%' など) を使用すると、インデックスはあまり役に立ちません。

MySQL がインデックスに列を含めることができるかどうかは特に覚えていませんが、そうであれば、select ステートメントに含まれているが where 句には含まれていない列をインデックスに含まれる列として追加することで、追加の利点が得られる可能性があります。

それ以外の場合、実行される実際の操作は、インデックス シークとキー ルックアップです。インデックスには常に、関連付けられたデータ行の主キーが含まれる列として含まれているため、インデックス キーが見つかると、行はその主キーを介して検索されます。キー ルックアップを回避できれば、クエリの IO コストを大幅に削減できます。もちろん、これにより、挿入と更新のコストがわずかに増加し、データベースが占めるスペースが大幅に増加します。

于 2012-06-19T23:17:34.240 に答える