データの重複登録回避方法は?

perlに限ったことではありませんが、DB処理を行う際、データの重複登録を回避するにはどういう方法をとるのがよいのでしょうか?

WEBアプリケーションの場合、ページリロードによるデータの重複登録が発生することがあり、それをうまく回避する方法はないかと模索中です。

ぱっと思いついたのは、

  • データ登録を行う前にチェック用のSQLを実行し、データが存在しなければ登録する方法
  • 登録の際に例外処理を行い、意図的に例外を握りつぶす方法

の2通りです。

サンプルとして、id(主キー)とnameを持つmemberテーブルを作ってみます。

テーブル作成
mysql> show create table member\G
*************************** 1. row ***************************
       Table: member
Create Table: CREATE TABLE `member` (
  `id` bigint(20) NOT NULL,
  `name` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)


mysql> desc member;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | bigint(20)  | NO   | PRI | NULL    |       |
| name  | varchar(64) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
1.データ登録の前にチェック用SQLを実行して重複登録を回避する方法

以下は、コマンドラインから引数(idとname)を受け取りデータ登録を行うサンプル

#usr/bin/perl
use strict;
use warnings;
use DBI;
use Data::Dumper;

my ($id, $name) = @ARGV;
die unless (@ARGV == 2);

my $dbh = DBI->connect(
        'dbi:mysql:dbname=test',
        'test_user',
        'test',
        { RaiseError => 1, PrintError => 0, AutoCommit => 0 }
       );

# データの存在チェック
my $sth = $dbh->prepare("SELECT * FROM member WHERE id = ?");
$sth->execute($id);
my $data = $sth->fetchrow_hashref;

# データが存在しなければ新規登録
if (!$data) {
    $sth = $dbh->prepare("INSERT INTO member (id, name) VALUES(?, ?)");
    $sth->execute($id, $name);
    $dbh->commit;
}

$sth = $dbh->prepare("SELECT * FROM member");
$sth->execute();
print Dumper($sth->fetchall_arrayref);
$dbh->disconnect;
サンプル実行
新規登録
perl sample.pl 1 test1
$VAR1 = [
          [
            '1',
            'test1'
          ]
        ];

新規登録
perl sample.pl 2 test2
$VAR1 = [
          [
            '1',
            'test1'
          ],
          [
            '2',
            'test2'
          ]
        ];

重複登録
perl sample.pl 1 test1
$VAR1 = [
          [
            '1',
            'test1'
          ],
          [
            '2',
            'test2'
          ]
        ];


登録の前にチェック用SQLを実行することで、重複登録が回避できています。

なお、チェックなしにプログラムを実行すると、重複登録になりエラーが発生します。

perl sample.pl 1 test1
DBD::mysql::st execute failed: Duplicate entry '1' for key 'PRIMARY' at exception_sample2.pl line 23.
Issuing rollback() due to DESTROY without explicit disconnect() of DBD::mysql::db handle dbname=test at exception_sample.pl line 23.
2.例外処理を使って重複登録を回避する方法

処理内容自体は1.の方法と同じですが、重複登録の回避方法としてevalを用いています。

#usr/bin/perl
use strict;
use warnings;
use DBI;
use Data::Dumper;

my ($id, $name) = @ARGV;
die unless (@ARGV == 2);

my $dbh = DBI->connect(
        'dbi:mysql:dbname=test',
        'test_user',
        'test',
        { RaiseError => 1, PrintError => 0, AutoCommit => 0 }
       );

my $sth = $dbh->prepare("INSERT INTO member (id, name) VALUES(?, ?)");
eval {
    # 重複登録の場合、例外発生
    $sth->execute($id, $name);
    $dbh->commit;
};

# 何もしない(意図的に例外を握りつぶす)
# if ($@) {
#
# }

$sth = $dbh->prepare("SELECT * FROM member");
$sth->execute();
print Dumper($sth->fetchall_arrayref);
$dbh->disconnect;

実行結果は1.と同じです。

evalを使って例外処理を行い、意図的に例外を握りつぶすことで、重複登録になってもプログラムは途中終了せず、データの表示が行われます。

自分の中では1.の方法よりも2.の方法のほうがスッキリしていると思うのですが、実際のところどういう方法がいいのか分からず悩んでいます。

他にももっと良い方法があるのでしょうか?