MySQLでUUIDをPrimary Keyとして使用する際の問題と解決方法
1. UUIDをPKとして使用した場合の問題
-
UUIDの特性: UUIDは時間とランダム性に基づいて生成されるため、無作為に分布します。このため、インデックス構造上、データがB-Treeに挿入される際に、データがソートされる必要があり、多くのディスクI/Oが発生します。
-
ディスクI/Oの非効率性: MySQLでは、PKは基本的にクラスター化インデックスを使用するため、データ挿入時にソートが維持される必要があります。UUIDの無作為性が、クラスター化インデックスの効率を低下させます。その結果、挿入パフォーマンスが低下し、ディスクI/Oのコストが増加します。
-
パフォーマンス問題: UUIDをPKとして使用した場合、ディスクI/Oの増加によりパフォーマンスが大幅に低下し、挿入速度が遅くなり、テーブルサイズが不必要に大きくなります。
-
2. UUIDをPKとして使用しない解決方法
UUIDの一意性を保ちながら挿入パフォーマンスを向上させるための解決策は、自動増加するBIGINTタイプのPKを使用することです。
修正されたテーブル構造の例
CREATE TABLE `users` (
`pk` BIGINT NOT NULL AUTO_INCREMENT,
`uuid` BINARY(16) NOT NULL,
`name` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_general_ci',
`email` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_general_ci',
`password` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_bin',
`is_delete` TINYINT(1) NOT NULL,
`create_data_at` TIMESTAMP NOT NULL,
`update_data_at` TIMESTAMP NOT NULL,
PRIMARY KEY (`pk`) USING BTREE,
INDEX `uuid` (`uuid`) USING BTREE,
INDEX `idx_users_name` (`name`) USING BTREE
)
COMMENT='users'
COLLATE='utf8mb4_bin'
ENGINE=InnoDB;
- PKとしてBIGINTを使用: 自動増加するBIGINTをPKとして使用し、データ挿入時のソートを容易にします。
- UUIDは補助インデックスとして使用: UUIDフィールドは補助インデックスとして使用され、データの検索や関係性を保つために使用されます。
この方法により、挿入パフォーマンスを改善しながらUUIDの一意性を保つことができます。
3. KotlinでのUUID処理
KotlinでUUIDを簡単に使用できるようにした例は以下の通りです。
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
var uuid: UUID? = null
@Column(name = "create_data_at", updatable = false)
@CreatedDate
lateinit var createdAt: Instant
protected set
@LastModifiedDate
@Column(name = "update_data_at")
lateinit var updatedAt: Instant
protected set
@Column(name = "is_delete")
var isDelete: Boolean = false
}
@Entity(name = "users")
class UsersEntity(
@Column
var name: String = "",
@Column
var email: String = "",
@Column
var password: String = ""
): BaseEntity()
BaseEntityクラスではUUIDを使用し、UsersEntityクラスはこのクラスを継承して、作成日時、更新日時、削除フラグを管理します。
これにより、プログラム上でUUIDを簡単に扱える環境が提供されます。