私はこれについてリック・ジェームズとかなり長い議論をしました。これは、intが20億近くに制限されている自動インクリメントpkを置き換える複合キーを持つというアイデアで思いつきました。私のテーブルは、毎月数億近くのデータをキャプチャしているため、数か月でこの制限に簡単に到達します。以下は私のテーブルの様子です。キーテーブルはgdata
なので、3つのフィールドPRIMARY KEY (alarmTypeID,vehicleID,gDateTime)
を使用してプライマリを合成します。次に、アラームテーブルと呼ばれる別のテーブルがあります。両方のリンクは1対多です。 gdata
内の1つのデータが0個以上のalarms
に関連している可能性があることを意味します。それらの間のリンクはvehicleID
とgDateTime
です。
CREATE TABLE `gdata` (
`alarmTypeID` tinyint(4) NOT NULL DEFAULT '0',
`fleetID` smallint(11) NOT NULL,
`fleetGroupID` smallint(11) DEFAULT NULL,
`fleetSubGroupID` smallint(11) DEFAULT NULL,
`deviceID` mediumint(11) NOT NULL,
`vehicleID` mediumint(11) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`latitude` float NOT NULL,
`longitude` float NOT NULL,
`speed` smallint(11) NOT NULL
-- (see full text)
) ;
ALTER TABLE `gdata`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`),
ADD KEY `gDateTime` (`gDateTime`),
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`);
COMMIT;
こちらが警報表です
CREATE TABLE `alarm` (
`alarmTypeID` tinyint(4) NOT NULL,
`vehicleID` mediumint(9) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`alarmValue` varchar(5) NOT NULL,
`readWeb` enum('n','y') NOT NULL DEFAULT 'n',
`readWebDateTime` datetime NOT NULL,
`readMobile` enum('n','y') NOT NULL DEFAULT 'n',
`readMobileDateTim` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `alarm`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`);
COMMIT;
すべてがよさそうですが、最近私は関連するトピックについてグーグルをやっていて、それがいくつかの議論であることを発見しました https://www.quora.com/Is-it-a-bad-idea-to-have-a-primary- key-on-3-or-more-columns は複合主キーに反し、主に挿入目的で自動インクリメントを使用することを好みます。プライマリの複合キーを維持したり、自動インクリメントに戻したりするために、これにさらに光を当てることができますか?
複合キーに問題はありません。ただし、 (InnoDB
がデータを格納する方法 を考慮する必要があります。
上記のリンクされたドキュメントを引用:
各InnoDBテーブルのデータはページに分割されます。各テーブルを構成するページは、Bツリーインデックスと呼ばれるツリーデータ構造に配置されます。テーブルデータとセカンダリインデックスはどちらもこのタイプの構造を使用します。テーブル全体を表すBツリーインデックスは、クラスター化インデックスと呼ばれ、に従って編成されます。主キー列。インデックスデータ構造のノードには、その行のすべての列(クラスター化インデックスの場合)、またはインデックス列と主キー列(セカンダリインデックスの場合)の値が含まれます。
つまり、InnoDBはPRIMARY KEY
に従ってデータを格納します。挿入するデータのPKが増加している場合、ページの断片化は発生しません。それは常にAUTO_INCREMENT
で起こります。データを年代順で挿入する場合(つまり、gDateTime
は常に単調増加します)、順序を変更しますPKを構成する列のうち、
PRIMARY KEY (`gDateTime`, `alarmTypeID`, `vehicleID`)
...「新しい行を他の行の中央に合わせる」必要がないという点で同じ利点があります(つまり、Bツリーは挿入ごとに断片化されません)。
ただし、このテーブルを他の(関連する)テーブルから参照する場合は、参照するテーブルに常にPK(gDateTime
、alarmTypeID
、vehicleID
)を格納する必要があります。これは、7バイトまたは8バイトのストレージを毎回保存することを意味します。複合PKは2 + 1 + 8 = 11バイトの情報を使用します(おそらく、アライメントのために12バイトを使用します)。一方、INT UNSIGNED AUTO_INCREMENT
は、参照テーブルで4バイトのみを使用します。 PKには2 ^ 32の異なる値に制限されています。 2 ^ 32を超える値が必要な場合は、BIGINT AUTO_INCREMENT
が必要です。これにより、2 ^ 64が得られます(これが十分に大きくない実際的なケースはまだ見つかりません)。
これが理にかなっているかどうかは、特定のシナリオに大きく依存します。
joanoloにはいくつかの良い点があります。
DATETIME
およびTIMESTAMP
は、小数秒なしで、それぞれ5バイトを使用します。 (つまり、問題のPKは合計9バイトです。)DATETIME
またはTIMESTAMP
キーでPRIMARY
(またはUNIQUE
)を使用するのは危険です。2つのエントリが同時に発生した場合はどうなりますか? (この質問はアプリケーションによって異なります。たとえば、トラックの位置を測定する場合、1秒以内に2つのGPS読み取り値は必要ありません。)INT
をオーバーフローする恐れがあります。AUTO_INCREMENT
の次の選択肢は8バイトのBIGINT
です。 9バイトと大差ありません。MEDIUMINT
は3バイトです(vehicleID
)。詳細...
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`), -- 1+3+5 = 0 bytes
ADD KEY `gDateTime` (`gDateTime`), -- 5 + 1+3 = 9
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`); -- 2+3+5 + 1 = 11
PKは残りの列に含まれているため、0バイトと言います。セカンダリキーの数値は、セカンダリキー列のサイズ+追加のPK列です。 (もちろん、インデックスにはかなりのオーバーヘッドがあるため、これらの数値を使用してBTreeの最終的なサイズを計算することはできません。ファッジファクターとして3倍が必要になる場合があります。)
SELECT
WHERE alarmTypeID = constant
AND vehicleID = constant
AND gDateTime ... (some range)
(alarmTypeID
、vehicleID
、gDateTime
よりも(gDateTime
、alarmTypeID
、vehicleID
)の方がはるかに適切に処理されます)。これが一般的なクエリである場合、断片化を避けたいという欲求を上回ると私は主張します。
PRIMARY KEY(alarmTypeID
、vehicleID
、gDateTime
)は、2次キーとデータの間のバウンスを回避します。
PRIMARY KEY(gDateTime
、alarmTypeID
、vehicleID
)は、アラームや車両を使用できず、対象外のアラームや車両をまたぐ必要があります。または、セカンダリキーを使用して、前後にバウンスするようにします。どちらの場合も、はるかに遅くなります。 (Rule of Thumb:データがキャッシュされていない場合、回転ディスクの場合は10倍遅くなります。)