防止在不使用索引的情况下插入重复项

Kar*_*l S 5 mysql sql indexing mariadb

我有一个users大致像这样的MariaDB表:

id INT PRIMARY KEY AUTOINCREMENT,
email_hash INT, -- indexed
encrypted_email TEXT,
other_stuff JSON
Run Code Online (Sandbox Code Playgroud)

出于隐私原因,我无法将实际的电子邮件存储在数据库中。

用于电子邮件的加密不是一对一的,即可以将一封电子邮件加密为许多不同的加密表示形式。这使得仅仅在encrypted_email列上添加索引毫无意义,因为它永远不会捕获重复项。

数据库中已经有数据,更改加密方法或散列方法毫无疑问。

email_hash列也不能具有唯一索引,因为它应该是一个短哈希,以加快重复检查的速度。它不能太独特,因为它将使所有隐私保证无效。

如何防止带有相同电子邮件的两个条目出现在数据库中?

另一个限制:LOCK TABLE根据文档https://mariadb.com/kb/en/library/lock-tables/,我可能无法使用,

LOCK TABLES使用Galera群集时不起作用。与Galera一起使用时,您可能会遇到崩溃或锁定的情况。

LOCK TABLES隐式提交活动事务(如果有)。另外,启动事务总是会释放通过获取的所有表锁LOCK TABLES

(我确实使用Galera,并且确实需要事务,因为插入新用户会伴随其他一些插入和更新)


由于后端应用程序服务器(整体式)只要不存储个人信息即可(例如,用于发送电子邮件,验证登录名等)处理个人信息,因此我在应用程序中进行重复检查。

目前,我正在执行以下操作(伪代码):

perform "START TRANSACTION"
h := hash(new_user.email)
conflicts := perform "SELECT encrypted_email FROM users WHERE email_hash = ?", h
for conflict in conflicts :
    if decrypt(conflict) == new_user.email :
        perform "ROLLBACK"
        return DUPLICATE
e := encrypt(new_user.email)
s := new_user.other_stuff
perform "INSERT INTO users (email_hash, encrypted_email, other_stuff) VALUES (?,?,?)", h, e, s
perform some other inserts as part of the transaction
perform "COMMIT"
return OK
Run Code Online (Sandbox Code Playgroud)

如果两次尝试之间有时间间隔,则可以正常工作。但是,当两个线程尝试同时添加同一用户时,则两个事务并行运行,进行选择,看到没有冲突的重复项,然后两个都继续添加用户。如何防止这种情况,或者至少是从容地立即恢复?


这是比赛的样子,简化了:

  • 两个线程开始他们的交易

  • 两个线程都执行选择,并且在两种情况下选择均返回零行。

  • 两个线程都假定不会重复。

  • 两个线程都添加用户。

  • 两个线程都提交事务。

  • 现在有两个用户使用相同的电子邮件。

Ric*_*mes 4

FOR UPDATE在 的末端SELECT

另外,由于您使用的是 Galera,因此必须在 后检查错误COMMIT。(即报告与其他节点发生冲突时。)

  • @KarolS - 在这种情况下,“FOR UPDATE”宣布可以在该区域插入行。 (2认同)