1 2 3 4 5
my $id = $self->insert_foo($hunt) || $self->update_foo($hunt); # $hunt ist eine vogelwilde Datenstruktur mit allen # Daten, die von einer DB zu einer anderen DB übertragen werden # Idee: bless $hunt, 'InsertClass';
2012-05-25T21:25:27 rostiZurück zu MySQL, die Last_Insert_Id abzufragen ist eine einfache Möglichkeit, herauszufinden, ob ein Insert stattgefunden hat, bei einem Update liefert diese Anfrage eine 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mysql> select * from foo;
+----+------+
| id | name |
+----+------+
| 1 | foo |
| 2 | foo1 |
| 3 | foo2 |
+----+------+
3 rows in set (0.00 sec)
mysql> insert into bar (name) VALUES ("test");
Query OK, 1 row affected (0.00 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 3 |
+------------------+
1 row in set (0.00 sec)
mysql> insert into foo (name) VALUES ("foo") on duplicate key update name=name;
Query OK, 0 rows affected (0.00 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 3 |
+------------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
mysql> insert into foo (name) VALUES ("foo") on duplicate key update name=name, id=LAST_INSERT_ID(id);
Query OK, 0 rows affected (0.00 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 1 |
+------------------+
1 row in set (0.00 sec)
Quotewie gesagt, es ist nicht trivial, und wenn du es benutzt, solltest du dir über sowas im klaren sein.
2012-05-26T06:31:53 rostiQuotewie gesagt, es ist nicht trivial, und wenn du es benutzt, solltest du dir über sowas im klaren sein.
Ja natürlich, ist klar ;)
QuoteEdit: Es ist ja auch unsinnig, _jeder_ Tabelle eine autoincrement-Spalte zu verpassen.
QuoteMeine Threaderöffnung ist übrigens auch eine selbstkritische Betrachtung.
2012-05-25T21:25:27 rostiAlso, von der Sache her werden IDs nur dann gebraucht, wenn in weiteren, verknüpften Tabellen neue Datensätze hinzukommen, was bei einem Update in Fakt nicht der Fall ist
Quotedas ist wieder so ein punkt, der pauschal überhaupt nicht gilt. ich habe einen string, will ihn in einer tabelle ablegen und brauche die id, um in einer weiteren tabelle einen neuen datensatz mit dieser id einzufügen. und dabei ist es für die weitere tabelle total uninteressant, ob der datensatz neu ist oder schon da war. ich brauche in jedem fall die id, auch wenn kein insert stattgefunden hat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use DBI; local $\ = local $, = "\n"; my $DBH = dbh(); my $q = q( INSERT INTO test(text)VALUES(?) ON Duplicate Key UPDATE text=? ); my $STH = $DBH->prepare($q); my $ins = '12asdf'; # Zum Testen Werte ändern.... my $state = $STH->execute($ins, $ins); # case state: 1 INSERT, 2 UPDATE if($state == 2){ print "Es war ein Update, wir brauchen die ID mit einer weiteren Abfrage!"; my $id = $DBH->selectrow_array("SELECT id FROM test WHERE text=?", {}, $ins); print "Hier ist sie: $id"; } elsif($state == 1){ print "Es war ein Insert, wir brauchen die ID als Last_Insert_Id!"; my $id = $DBH->selectrow_array("SELECT LAST_INSERT_ID()"); print "Hier ist sie: $id"; } else{ die "Wiedermal ist alles schiefgegangen!" } # Frage: Mit welchem Feld letztendlich wird die referenzielle Integrität hergestellt? # Antwort: Mit dem Feld, welches in der Haupt-Tabelle als Unique Key definiert ist! # Nächste Frage: Wozu brauchen wir dann einen auto_increment Wert? # Antwort 1: Wenn wir alles so lassen wie es ist, brauchen wir die auto_increment ID. # Antwort 2: Wenn wir das DB-Design geschickt ändern, brauchen wir diese ID nicht mehr. ########################################################################### sub dbh{ my $class = shift; # Name der o.g. Package my %cfg = ( base => 'myweb', host => 'localhost', port => 3306, user => '', pass => '', @_); my $dbh = undef; eval{ $dbh = DBI->connect("DBI:mysql:$cfg{base}:$cfg{host}:$cfg{port}", $cfg{user}, $cfg{pass}, {RaiseError => 1, PrintError => 0} ); }; return $@ ? undef : $dbh; } __END__ CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `text` varchar(20) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `text` (`text`) ) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
$STH->execute($ins, $ins)
2012-05-26T12:07:11 janhast du mal einen benchmark laufen lassen, was schneller ist?
Ich finde deinen Code eher unschön, a) datenbankspezifisch und dann b)Code: (dl )$STH->execute($ins, $ins)
das wird doch superhässlich und unübersichtlich, wenn du 10 felder befüllst.
2012-05-27T08:00:20 rostiMeine diesbezüglichen Gedanken kommen nicht von ungefähr, täglich habe ich Unmengen von Daten aufzuarbeiten,
QuoteHinsichtlich Performance gibt es noch Einiges zu optimieren und einige der Bremsen sind eben genau solche Sachen, wie hier beschrieben.
Quotewieviel dir das ON DUPLICATE wirklich bringt, würde ich schonmal messen. blind zu optimieren macht keinen spass und bringt dir auch nix für die zukunft.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Mit DELETE FROM, Tabellen sind leer
Benchmark: timing 50000000 iterations of AutoIncr, Redesign...
AutoIncr: 0 wallclock secs ( 1.58 usr + 0.00 sys = 1.58 CPU) @ 31685678.07/s (n=50000000)
Redesign: 0 wallclock secs ( 0.12 usr + -0.01 sys = 0.11 CPU) @ 454545454.55/s (n=50000000)
(warning: too few iterations for a reliable count)
Rate AutoIncr Redesign
AutoIncr 31685678/s -- -93%
Redesign 454545455/s 1335% --
Ohne DELETE FROM
Benchmark: timing 50000000 iterations of AutoIncr, Redesign...
AutoIncr: 3 wallclock secs ( 2.53 usr + 0.00 sys = 2.53 CPU) @ 19747235.39/s (n=50000000)
Redesign: 0 wallclock secs ( 0.06 usr + 0.00 sys = 0.06 CPU) @ 806451612.90/s (n=50000000)
(warning: too few iterations for a reliable count)
Rate AutoIncr Redesign
AutoIncr 19747235/s -- -98%
Redesign 806451613/s 3984% --
Wiederholung:
Benchmark: timing 50000000 iterations of AutoIncr, Redesign...
AutoIncr: 1 wallclock secs ( 1.02 usr + 0.00 sys = 1.02 CPU) @ 49261083.74/s (n=50000000)
Redesign: 1 wallclock secs ( 1.00 usr + 0.00 sys = 1.00 CPU) @ 50000000.00/s (n=50000000)
Rate AutoIncr Redesign
AutoIncr 49261084/s -- -1%
Redesign 50000000/s 1% --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
#/usr/bin/perl use strict; use warnings; use Benchmark qw(cmpthese timethese); use DBI; my $ins = 'abcde'; my $DBH = dbh(); my $STH = { INS_AUTO => $DBH->prepare("INSERT INTO test(text)VALUES(?)"), IS_DRIN => $DBH->prepare("SELECT id FROM test WHERE text=?"), LAST_ID => $DBH->prepare("SELECT LAST_INSERT_ID()"), REDESIGN => $DBH->prepare(q( INSERT INTO redesign(text)VALUES(?) ON Duplicate Key UPDATE text=? )), }; # bisherige Lösung, vorh. Abfrage | Last_Insert_ID sub autoincr{ $DBH->do("DELETE FROM test"); $STH->{IS_DRIN}->execute($ins); my $id = 0; if($id = $STH->{IS_DRIN}->fetchrow_array){ return $id; } else{ $STH->{INS_AUTO}->execute($ins); $id = $STH->{LAST_ID}->execute; $id = $STH->{LAST_ID}->fetchrow_array; return $id; } } # Lösung nach redesign sub redesign{ $DBH->do("DELETE FROM redesign"); $STH->{REDESIGN}->execute($ins, $ins); return $ins; } cmpthese(50_000_000, { 'Redesign' => &redesign, 'AutoIncr' => &autoincr, }); sub dbh{ my %cfg = ( base => 'myweb', host => 'localhost', port => 3306, user => '', pass => '', @_); my $dbh = undef; eval{ $dbh = DBI->connect("DBI:mysql:$cfg{base}:$cfg{host}:$cfg{port}", $cfg{user}, $cfg{pass}, {RaiseError => 1, PrintError => 0} ); }; return $@ ? undef : $dbh; } __END__ mysql> show create table test; CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `text` varchar(20) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 mysql> show create table redesign; redesign | CREATE TABLE `redesign` ( `text` varchar(29) NOT NULL DEFAULT '', PRIMARY KEY (`text`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#/usr/bin/perl
use strict;
use warnings;
use Benchmark qw(cmpthese timethese);
use DBI;
my $ins = 'abcde';
my $loop_auto = 0;
my $loop_rosti = 0;
my $DBH = dbh();
my $STH = {
INS_AUTO => $DBH->prepare("INSERT INTO test(text)VALUES(?)"),
IS_DRIN => $DBH->prepare("SELECT id FROM test WHERE text=?"),
LAST_ID => $DBH->prepare("SELECT LAST_INSERT_ID()"),
REDESIGN => $DBH->prepare(q(
INSERT INTO redesign(text)VALUES(?)
ON Duplicate Key UPDATE text=?
)),
};
cmpthese(250000, {
'Redesign' => sub {
$loop_rosti++;
my $test = $ins . ($loop_rosti % 2 ? $loop_rosti : $loop_rosti - 1);
$STH->{REDESIGN}->execute($test, $test);
},
'AutoIncr' => sub {
my $test = $ins . ($loop_auto % 2 ? $loop_auto : $loop_auto - 1);;
$STH->{IS_DRIN}->execute($test);
my $id = 0;
if($id = $STH->{IS_DRIN}->fetchrow_array){
}
else{
$STH->{INS_AUTO}->execute($test);
$id = $STH->{'mysql_insertid'};
}
},
}
);
sub dbh{
my %cfg = (
base => 'testincr',
host => 'localhost',
port => 3306,
user => '',
pass => '',
@_);
my $dbh = undef;
eval{
$dbh = DBI->connect("DBI:mysql:$cfg{base}:$cfg{host}:$cfg{port}", $cfg{user}, $cfg{pass},
{RaiseError => 1, PrintError => 0}
);
};
return $@ ? undef : $dbh;
}
__END__
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
key(`text`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `redesign` (
`text` varchar(29) NOT NULL DEFAULT '',
PRIMARY KEY (`text`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
2012-05-29T08:39:51 pqich glaube es geht nicht ums update, sondern um das schema an sich. wenn du den datensatz irgendwo in einer anderen tabelle referenzieren willst, musst du dort immer das textfeld benutzen. ein integer als pk wäre da wohl deutlich platzsparender.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# nutze die Möglichkeit der DB Engine # und nutze einen eigenen Cache sub insert_engine{ state %cache = (); # feature 5.010 my $r = shift; my @args = ($r->{user_id}, $r->{user_name}); my $ck = $r->{user_id}.$r->{user_name}; $cache{$ck} ||= eval{ print "Anfrage\n"; # test ob der cache funktioniert $STH_INSERT_ON_DUPLICATE_KEY->execute(@args); $DBH->selectrow_array("SELECT LAST_INSERT_ID()"); }; return $@ ? undef : $cache{$ck}; }
2012-06-10T18:18:11 rostiWas MySQL betrifft gibt es doch noch eine kleine Sache, die ich bis jetzt übersehen habe:
LAST_INSERT_ID()
LAST_INSERT_ID(expr)
...
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)