diff --git a/mo/users.py b/mo/users.py
index e71d9724eabda491c235cf893c0c964e8794d221..b57c6e0557861c16c31f7ac9afb63089d2d94aeb 100644
--- a/mo/users.py
+++ b/mo/users.py
@@ -7,6 +7,7 @@ import email.errors
 import email.headerregistry
 import re
 import secrets
+from sqlalchemy.dialects.postgresql import insert as pgsql_insert
 from typing import Optional, Tuple
 
 import mo
@@ -96,40 +97,57 @@ def change_user_to_org(user, reason: str):
 
 def find_or_create_user(email: str, krestni: Optional[str], prijmeni: Optional[str], is_org: bool, reason: str, allow_change_user_to_org=False) -> Tuple[db.User, bool, bool]:
     sess = db.get_session()
-    user = sess.query(db.User).with_for_update().filter_by(email=email).one_or_none()
-    is_new = user is None
+    user = sess.query(db.User).filter_by(email=email).one_or_none()
+    is_new = False
     is_change_user_to_org = False
+
     if user is None:  # HACK: Podmínku je nutné zapsat znovu místo užití is_new, jinak si s tím mypy neporadí
         if not krestni or not prijmeni:
             raise mo.CheckError('Osoba s daným e-mailem zatím neexistuje, je nutné uvést její jméno.')
-        user = db.User(email=email, first_name=krestni, last_name=prijmeni, is_org=is_org)
-        sess.add(user)
-        sess.flush()    # Aby uživatel dostal user_id
-        logger.info(f'{reason.title()}: Založen uživatel user=#{user.user_id} email=<{user.email}>')
-        mo.util.log(
-            type=db.LogType.user,
-            what=user.user_id,
-            details={'action': 'create-user', 'reason': reason, 'new': db.row2dict(user)},
+
+        res = sess.connection().execute(
+            pgsql_insert(db.User.__table__)
+            .values(
+                email=email,
+                first_name=krestni,
+                last_name=prijmeni,
+                is_org=is_org,
+            )
+            .on_conflict_do_nothing()
+            .returning(db.User.user_id)
         )
-    else:
-        if (krestni and user.first_name != krestni) or (prijmeni and user.last_name != prijmeni):
-            raise mo.CheckError(f'Osoba již registrována s odlišným jménem {user.full_name()}')
-        if (user.is_admin or user.is_org) != is_org:
-            if is_org:
-                if allow_change_user_to_org:
-                    change_user_to_org(user, reason)
-                    is_change_user_to_org = True
-                else:
-                    raise CheckErrorOrgIsUser('Nelze předefinovat účastníka na organizátora.')
+
+        user = sess.query(db.User).filter_by(email=email).one()
+
+        if res.fetchall():
+            is_new = True
+            logger.info(f'{reason.title()}: Založen uživatel user=#{user.user_id} email=<{user.email}>')
+            mo.util.log(
+                type=db.LogType.user,
+                what=user.user_id,
+                details={'action': 'create-user', 'reason': reason, 'new': db.row2dict(user)},
+            )
+
+    if (krestni and user.first_name != krestni) or (prijmeni and user.last_name != prijmeni):
+        raise mo.CheckError(f'Osoba již registrována s odlišným jménem {user.full_name()}')
+    if (user.is_admin or user.is_org) != is_org:
+        if is_org:
+            if allow_change_user_to_org:
+                change_user_to_org(user, reason)
+                is_change_user_to_org = True
             else:
-                raise mo.CheckError('Nelze předefinovat organizátora na účastníka.')
+                raise CheckErrorOrgIsUser('Nelze předefinovat účastníka na organizátora.')
+        else:
+            raise mo.CheckError('Nelze předefinovat organizátora na účastníka.')
+
     return user, is_new, is_change_user_to_org
 
 
 def find_or_create_participant(user: db.User, year: int, school_id: Optional[int], birth_year: Optional[int], grade: Optional[str], reason: str) -> Tuple[db.Participant, bool]:
     sess = db.get_session()
     part = sess.query(db.Participant).get((user.user_id, year))
-    is_new = part is None
+    is_new = False
+
     if part is None:
         prev_part = sess.query(db.Participant).filter_by(user_id=user.user_id).order_by(db.Participant.year.desc()).limit(1).one_or_none()
         if not school_id:
@@ -144,20 +162,37 @@ def find_or_create_participant(user: db.User, year: int, school_id: Optional[int
                 raise mo.CheckError('Osoba s daným e-mailem zatím není zaregistrovaná do ročníku, je nutné uvést rok narození.')
         if not grade:
             raise mo.CheckError('Osoba s daným e-mailem zatím není zaregistrovaná do ročníku, je nutné uvést ročník.')
-        part = db.Participant(user=user, year=year, school=school_id, birth_year=birth_year, grade=grade)
-        sess.add(part)
-        sess.flush()    # Kvůli logování
-        logger.info(f'{reason.title()}: Založen účastník #{user.user_id}')
-        mo.util.log(
-            type=db.LogType.participant,
-            what=user.user_id,
-            details={'action': 'create-participant', 'reason': reason, 'new': db.row2dict(part)},
+
+        res = sess.connection().execute(
+            pgsql_insert(db.Participant.__table__)
+            .values(
+                user_id=user.user_id,
+                year=year,
+                school=school_id,
+                birth_year=birth_year,
+                grade=grade,
+            )
+            .on_conflict_do_nothing()
+            .returning(db.Participant.user_id)
         )
-    else:
-        if ((school_id and part.school != school_id)
-                or (grade and part.grade != grade)
-                or (birth_year and part.birth_year != birth_year)):
-            raise mo.CheckError('Účastník již zaregistrován s odlišnou školou/ročníkem/rokem narození')
+
+        part = sess.query(db.Participant).get((user.user_id, year))
+        assert part is not None
+
+        if res.fetchall():
+            is_new = True
+            logger.info(f'{reason.title()}: Založen účastník #{user.user_id}')
+            mo.util.log(
+                type=db.LogType.participant,
+                what=user.user_id,
+                details={'action': 'create-participant', 'reason': reason, 'new': db.row2dict(part)},
+            )
+
+    if ((school_id and part.school != school_id)
+            or (grade and part.grade != grade)
+            or (birth_year and part.birth_year != birth_year)):
+        raise mo.CheckError('Účastník již zaregistrován s odlišnou školou/ročníkem/rokem narození')
+
     return part, is_new
 
 
@@ -166,28 +201,47 @@ def find_or_create_participation(user: db.User, contest: db.Contest, place: Opti
         place = contest.place
 
     sess = db.get_session()
-    pions = (sess.query(db.Participation)
-             .filter_by(user=user)
-             .filter(db.Participation.contest.has(db.Contest.round == contest.round))
-             .all())
+    pion = None
+    is_new = False
+    retry = False
+
+    while pion is None:
+        pions = (sess.query(db.Participation)
+                 .filter_by(user=user)
+                 .filter(db.Participation.contest.has(db.Contest.round == contest.round))
+                 .all())
+
+        if len(pions) == 0:
+            assert not retry
+            retry = True
+            res = sess.connection().execute(
+                pgsql_insert(db.Participation.__table__)
+                .values(
+                    user_id=user.user_id,
+                    contest_id=contest.contest_id,
+                    place_id=place.place_id,
+                    state=db.PartState.active,
+                )
+                .on_conflict_do_nothing()
+                .returning(db.Participation.user_id)
+            )
+            if res.fetchall():
+                is_new = True
+        elif len(pions) == 1:
+            pion = pions[0]
+        else:
+            raise mo.CheckError('Již se tohoto kola účastní ve více oblastech, což by nemělo být možné')
+
+    if pion.place != place:
+        raise mo.CheckError(f'Již se tohoto kola účastní v {contest.round.get_level().name_locative("jiném", "jiné", "jiném")} ({pion.place.get_code()})')
 
-    is_new = pions == []
     if is_new:
-        pion = db.Participation(user=user, contest=contest, place_id=place.place_id, state=db.PartState.active)
-        sess.add(pion)
-        sess.flush()    # Kvůli logování
         logger.info(f'{reason.title()}: Založena účast user=#{user.user_id} contest=#{contest.contest_id} place=#{place.place_id}')
         mo.util.log(
             type=db.LogType.participant,
             what=user.user_id,
             details={'action': 'add-to-contest', 'reason': reason, 'new': db.row2dict(pion)},
         )
-    elif len(pions) == 1:
-        pion = pions[0]
-        if pion.place != place:
-            raise mo.CheckError(f'Již se tohoto kola účastní v {contest.round.get_level().name_locative("jiném", "jiné", "jiném")} ({pion.place.get_code()})')
-    else:
-        raise mo.CheckError('Již se tohoto kola účastní ve více oblastech, což by nemělo být možné')
 
     return pion, is_new
 
diff --git a/mo/web/acct.py b/mo/web/acct.py
index d1497402b84a89a497b8f963d7b6c99c1f95b340..24f34bc2a38ac17937c03e88e57dd99a186e9365 100644
--- a/mo/web/acct.py
+++ b/mo/web/acct.py
@@ -193,19 +193,23 @@ def user_settings_personal():
             sess.commit()
             flash('Heslo změněno.', 'success')
         if form.email.data != user.email:
-            rr = mo.users.new_reg_request(db.RegReqType.change_email, request.remote_addr)
-            if rr:
-                rr.user_id = user.user_id
-                rr.email = form.email.data
-                sess.add(rr)
-                sess.commit()
-                app.logger.info(f'Settings: Požadavek na změnu e-mailu uživatele #{user.user_id}')
-                flash('Odeslán e-mail s odkazem na potvrzení nové adresy.', 'success')
-                mo.email.send_confirm_change_email(user, rr.email_token)
-            else:
-                app.logger.info('Settings: Rate limit')
-                flash('Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.', 'danger')
+            if mo.users.user_by_email(form.email.data) is not None:
+                flash('Tuto e-mailovou adresu už používá jiný uživatel.', 'danger')
                 ok = False
+            else:
+                rr = mo.users.new_reg_request(db.RegReqType.change_email, request.remote_addr)
+                if rr:
+                    rr.user_id = user.user_id
+                    rr.email = form.email.data
+                    sess.add(rr)
+                    sess.commit()
+                    app.logger.info(f'Settings: Požadavek na změnu e-mailu uživatele #{user.user_id}')
+                    flash('Odeslán e-mail s odkazem na potvrzení nové adresy.', 'success')
+                    mo.email.send_confirm_change_email(user, rr.email_token)
+                else:
+                    app.logger.info('Settings: Rate limit')
+                    flash('Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.', 'danger')
+                    ok = False
         if ok:
             return redirect(url_for('user_settings'))
 
@@ -436,6 +440,7 @@ class Reg2:
             RegStatus.new: 'Chybný potvrzovací kód. Zkontrolujte, že jste odkaz z e-mailu zkopírovali správně.',
             RegStatus.expired: 'Vypršela platnost potvrzovacího kódu, požádejte prosím o změnu e-mailu znovu.',
             RegStatus.already_spent: 'Tento odkaz na potvrzení změny e-mailu byl již využit.',
+            RegStatus.already_exists: 'Tuto adresu už použivá jiný účet.',
         },
         db.RegReqType.reset_passwd: {
             RegStatus.new: 'Chybný kód pro obnovení hesla. Zkontrolujte, že jste odkaz z e-mailu zkopírovali správně.',
@@ -498,38 +503,37 @@ class Reg2:
         email = mo.users.normalize_email(rr.email)      # Pro jistotu
         sess = db.get_session()
 
-        if db.get_session().query(db.User).with_for_update().filter_by(email=email).one_or_none():
+        try:
+            user, is_new, _ = mo.users.find_or_create_user(email, first_name, last_name, is_org=False, reason='register')
+        except mo.CheckError as e:
+            app.logger.info(f'Reg2: Založení účtu {email} selhalo: {e}')
+            self.status = RegStatus.already_exists
+            return False
+
+        if not is_new:
             # Účet mohl začít existovat mezi 1. a 2. krokem registrace
             app.logger.info(f'Reg2: Účet s e-mailem {email} začal během registrace existovat')
             self.status = RegStatus.already_exists
             return False
 
-        user = db.User(
-            email=email,
-            first_name=first_name,
-            last_name=last_name,
-        )
         mo.users.set_password(user, passwd)
-
+        mo.users.login(user)
         rr.used_at = mo.now
-        sess.add(user)
-        sess.flush()
 
-        app.logger.info(f'Reg2: Založen uživatel user=#{user.user_id} email=<{user.email}>')
-        mo.util.log(
-            type=db.LogType.user,
-            what=user.user_id,
-            details={'action': 'register', 'new': db.row2dict(user)},
-        )
-
-        mo.users.login(user)
         sess.commit()
         self.user = user
         return True
 
-    def change_email(self):
+    def change_email(self) -> bool:
         sess = db.get_session()
         user = self.rr.user
+
+        if mo.users.user_by_email(self.rr.email) is not None:
+            app.logger.info(f'Reg2: Uživatel #{user.user_id} si chce změnit email na <{user.email}>, ale už je použitý jiným účtem.')
+            self.status = RegStatus.already_exists
+            return False
+
+        # Tady je krátké okénko, kdy může nastat race condition. Chytí ji integritní omezení v DB a vznikne výjimka.
         user.email = self.rr.email
 
         app.logger.info(f'Reg2: Uživatel #{user.user_id} si změnil email na <{user.email}>')
@@ -544,6 +548,7 @@ class Reg2:
 
         self.rr.used_at = mo.now
         sess.commit()
+        return True
 
     def change_passwd(self, new_passwd: str):
         sess = db.get_session()
@@ -618,13 +623,15 @@ def confirm_email():
     form = ConfirmEmailForm()
     if form.validate_on_submit():
         if form.submit.data:
-            reg2.change_email()
-            flash('E-mail změněn.', 'success')
+            if reg2.change_email():
+                flash('E-mail změněn.', 'success')
+                return redirect(url_for('user_settings'))
         elif form.cancel.data:
             reg2.spend_request()
             flash('Požadavek na změnu e-mailu zrušen.', 'success')
-        return redirect(url_for('user_settings'))
+            return redirect(url_for('user_settings'))
 
+    reg2.flash_message()
     form.orig_email.data = reg2.rr.user.email
     form.new_email.data = reg2.rr.email
 
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index d9d8263e9a23bbdfe926939c67c091df4e6748cb..ed0eda1673c5859eddf9ca618c46418f44f466f0 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -368,7 +368,7 @@ class ParticipantsActionForm(FlaskForm):
 
             if self.remove_participation.data:
                 sess.delete(pion)
-                app.logger.info(f"Účast uživatele #{u.user_id} v soutěži #{pion.contest} zrušena")
+                app.logger.info(f"Web: Zrušena účast uživatele #{u.user_id} v soutěži #{pion.contest.contest_id}")
                 mo.util.log(
                     type=db.LogType.participant,
                     what=u.user_id,
@@ -387,7 +387,7 @@ class ParticipantsActionForm(FlaskForm):
 
                 if sess.is_modified(pion):
                     changes = db.get_object_changes(pion)
-                    app.logger.info(f"Účast uživatele #{u.user_id} upravena, změny: {changes}")
+                    app.logger.info(f"Web: Upravena účast uživatele #{u.user_id} v soutěži #{pion.contest.contest_id}, změny: {changes}")
                     mo.util.log(
                         type=db.LogType.participant,
                         what=u.user_id,
diff --git a/mo/web/user.py b/mo/web/user.py
index cc30786a34d495153151611256bbfbdb1bb268b7..677a09e735a2a936a6a3f5b62b8aa91da294e600 100644
--- a/mo/web/user.py
+++ b/mo/web/user.py
@@ -5,6 +5,7 @@ import flask_wtf.file
 import hashlib
 import hmac
 from sqlalchemy import and_
+from sqlalchemy.dialects.postgresql import insert as pgsql_insert
 from sqlalchemy.orm import joinedload
 from typing import List, Tuple, Optional
 import werkzeug.exceptions
@@ -221,7 +222,6 @@ def join_create_pion(c: db.Contest) -> None:
         state = db.PartState.registered
     p = db.Participation(user=g.user, contest=c, place=c.place, state=state)
     sess.add(p)
-    sess.flush()   # Kvůli logování
 
     logger.info(f'Join: Účastník #{g.user.user_id} přihlášen do soutěže #{c.contest_id}')
     mo.util.log(
@@ -410,22 +410,25 @@ def user_contest_task(contest_id: int, task_id: int):
                 flash(f'Chyba: {e}', 'danger')
                 return redirect(url_for('user_contest_task', contest_id=contest_id, task_id=task_id))
 
+            is_broken = paper.is_broken()
             sess.add(paper)
-
-            # FIXME: Bylo by hezké použít INSERT ... ON CONFLICT UPDATE
-            # (SQLAlchemy to umí, ale ne přes ORM, jen core rozhraním)
-            sol = (sess.query(db.Solution)
-                   .filter_by(task=task, user=g.user)
-                   .with_for_update()
-                   .one_or_none())
-            if sol is None:
-                sol = db.Solution(task=task, user=g.user)
-                sess.add(sol)
-            sol.final_submit_obj = paper
-
+            sess.flush()
+
+            sess.connection().execute(
+                pgsql_insert(db.Solution.__table__)
+                .values(
+                    task_id=task.task_id,
+                    user_id=g.user.user_id,
+                    final_submit=paper.paper_id,
+                )
+                .on_conflict_do_update(
+                    constraint='solutions_pkey',
+                    set_={'final_submit': paper.paper_id},
+                )
+            )
             sess.commit()
 
-            if paper.is_broken():
+            if is_broken:
                 flash('Soubor není korektní PDF, ale přesto jsme ho přijali a pokusíme se ho zpracovat. ' +
                       'Zkontrolujte prosím, že se na vašem počítači zobrazuje správně.',
                       'warning')