Avatar billede Slettet bruger
25. august 2004 - 21:00 Der er 14 kommentarer og
1 løsning

Uniqueness med InnoDB

Jeg er forvirret :)

Jeg prøver at lave et simpelt uniqueness check med InnoDB og
dens låse/transaktions mekanismer:

Eksisterer der en bruger med email adressen 'b'?
Hvis nej, så indsæt ham.

Hvordan laves dette således at 2 processer ikke kan konflikte?
(og uden brug af lock tables). 

Se følgende, hvor mysql_1 godt nok er sikker på at kunne
indsætte brugeren med email adressen 'b', men hvor mysql_2
fejler med en deadlock besked. Jeg ville gerne at mysql_2
slet ikke måtte udføre sin første select.

mysql_1 afspejler altså én process og mysql_2 en anden process.

Benytter repeatable read som transaktions niveau.

mysql_1> describe users;
+----------+-----------------------+------+-----+---------+----------------+
| Field    | Type                  | Null | Key | Default | Extra          |
+----------+-----------------------+------+-----+---------+----------------+
| id      | mediumint(8) unsigned |      | PRI | NULL    | auto_increment |
| address  | mediumtext            | YES  |    | NULL    |                |
| phone    | varchar(255)          | YES  |    | NULL    |                |
| mobile  | varchar(255)          | YES  |    | NULL    |                |
| email    | varchar(255)          |      | UNI |        |                |
| password | varchar(32)          |      |    |        |                |
| notes    | mediumtext            | YES  |    | NULL    |                |
| status  | enum('user','admin')  |      |    | user    |                |
| name    | varchar(255)          |      |    |        |                |
+----------+-----------------------+------+-----+---------+----------------+
9 rows in set (0.01 sec)

mysql_1> delete from users;
Query OK, 2 rows affected (0.00 sec)

mysql_1> begin;
Query OK, 0 rows affected (0.01 sec)

mysql_1> select * from users where email = 'b' for update;
Empty set (0.00 sec)

mysql_2> select * from users where email = 'b' for update;
Empty set (0.00 sec)

mysql_1> insert into users set email = 'b';

mysql_2> insert into users set email = 'b';
mysql_2> ERROR 1213: Deadlock found when trying to get lock; Try restarting transaction

mysql_1>
Query OK, 1 row affected (11.12 sec)

mysql_1> commit;
Query OK, 0 rows affected (0.00 sec)

mysql_1> select * from users;
+----+---------+-------+--------+-------+----------+-------+--------+------+
| id | address | phone | mobile | email | password | notes | status | name |
+----+---------+-------+--------+-------+----------+-------+--------+------+
| 11 | NULL    | NULL  | NULL  | b    |          | NULL  | user  |      |
+----+---------+-------+--------+-------+----------+-------+--------+------+
1 row in set (0.00 sec)

Endvidere siger nederste paragraf på
http://dev.mysql.com/doc/mysql/en/InnoDB_Next-key_locking.html,
at sådan et check kan laves med bare lock in share mode. Hvordan det
(i relation til ovenstående eksempel)?

Problemet med at gøre det, jeg prøver, tror jeg er, at innodb intet
låser idet resultatsættet er tomt. Og således kan mysql_2 fint udføre
sin første select.
Avatar billede arne_v Ekspert
25. august 2004 - 22:03 #1
Var det ikke langt nemmere at lave det med et unikt index ?
Avatar billede arne_v Ekspert
25. august 2004 - 22:08 #2
Jeg kan iøvrigt ikke se noget mystisk i output !

Hvis process 2 også havde lavet en begin, så burde dens select også have hængt,
men det gjorde den ikke og så er SELECT'en en enkelt transaktion og den er
jo repeatable read.

Process 2 insert laver en eller anden form for timeout fordi process 1 er
for længe om at lave commit.

Umiddelbart virker alt OK.
Avatar billede Slettet bruger
25. august 2004 - 22:11 #3
Min fejl ... mysql_2 laver også en begin før dens første select.
Men dens select retunerer 0 rækker med det samme alligevel! Det er dét,
der er lidt det underlige.

Process 2 laver ikke timeout, men afbryder fordi innodb detekterer deadlock.
Avatar billede Slettet bruger
25. august 2004 - 22:17 #4
arne_v : Og jow det er nemmere med et unikt index, og det er der da også. Email er unique. Jeg vil bare forstå hvorfor ovenstående ikke virker, som jeg troede det gjorde.
Avatar billede arne_v Ekspert
25. august 2004 - 22:25 #5
Hm.

Jeg har lige nærlæst lidt om transaction isolation levels. Så vidt jeg kan se
garanterer repeatable read at data i de records der returneres ikke har
ændret sig mens det at kræver serializable at sikre sig at der returneres
de samme records.
Avatar billede Slettet bruger
25. august 2004 - 22:53 #6
Lad mig spørge anderledes så ... hvordan ville du teste om noget eksisterede i tabellen, og hvis ikke så indsætte det? I 2 forskellige forespørgsler, og uden brug af lock table, og uden at det kan gå galt med flere tråde.
Avatar billede arne_v Ekspert
25. august 2004 - 22:55 #7
En normal SELECT for convenience og et unikt index for reelt at håndtere
samtidigheds problemet.
Avatar billede Slettet bruger
25. august 2004 - 23:00 #8
Men hvis du nu skal bruge resultatet af den første select til at starte noget andet (som du ikke let kan rulle tilbage, og som muligvis giver et resultat til din insert), så er det jo et problem at man ikke kan stole på den første select. Og først opdager problemet, når man ikke kan indsætte.

Altså:

SELECT ... (måske forkert)
HVIS INGEN RÆKKER
  LAV EN MASSE SJOV, SKRIV TIL NOGLE FILER, OG FÅ ET $RESULTAT
INSERT .. $RESULTAT .. (fejler duplicate key)
  UNDO EN MASSE HALLØJ (dette ville jeg gerne slippe for)
Avatar billede Slettet bruger
25. august 2004 - 23:01 #9
SELECT ... (måske forkert)
HVIS INGEN RÆKKER
  LAV EN MASSE SJOV, SKRIV TIL NOGLE FILER, OG FÅ ET $RESULTAT
INSERT .. $RESULTAT .. (fejler muligvis pga. duplicate key)
HVIS INSERT FEJL 
  UNDO EN MASSE HALLØJ (dette ville jeg gerne slippe for)
Avatar billede arne_v Ekspert
25. august 2004 - 23:05 #10
SELECT er kun convenience i forbindelse med noget bruger interface, således
at man tidligt kan fortælle at noget vil fejle inden brugeren taster en masse ind.

Det du gør er:

INSERT unik info + kendt data + dummy data for ukendt data
IF NOT fejl THEN
    do andet
    UPDATE data som først er kendt nu
ENDIF
Avatar billede Slettet bruger
25. august 2004 - 23:09 #11
Tak for tippet. Det vil jeg gøre fremover.

Kan du også forklare mig hvorfor mysql_2 _ikke_ venter ved sin første select på mysql_1? Havde der rent faktisk været en record med email = b, så _havde_ mysql_2 ventet på mysql_1.
Avatar billede arne_v Ekspert
25. august 2004 - 23:55 #12
Det tror jeg - jævnfør 22:25:24 kommentar
Avatar billede Slettet bruger
26. august 2004 - 00:43 #13
Jeg forstår det ikke. Hvis mysql_2 havde lavet LOCK IN SHARED MODE istedet, havde den stadig ikke ventet på mysql_1. mysql_2 ville derimod vente på mysql_1 når den skulle indsætte.
Avatar billede Slettet bruger
26. august 2004 - 00:43 #14
Men pyt være med det. Det andet du forklarede mig var sådan set vigtigere. Læg et svar for point.
Avatar billede arne_v Ekspert
26. august 2004 - 10:08 #15
transaction isolation level = interface
lock = implementation
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Computerworld tilbyder specialiserede kurser i database-management

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester