【发布时间】:2015-11-17 23:49:24
【问题描述】:
我正在寻找一种对字符串施加唯一约束的最佳方法。
我的用例是通过 SMTP 提要上的“消息 ID”和“回复中”字段将所有电子邮件链接在一起。
但是,由于消息的数量可能会增长到数百万,而且我们没有删除任何内容的计划,所以我需要一种方法来快速索引它们。问题是我的印象是字符串本身索引这些数字的速度较慢(如果我错了,请解释)。
到目前为止,我的解决方案是将消息 ID 转换为 sha256 哈希,然后转换为 8 x 32 位块 256 位数字,如下所示:
// Its not actually written in C
struct message_id {
int32_t id;
char[255] originalMessageId;
int32_t p01;
int32_t p02;
int32_t p03;
int32_t p04;
int32_t p05;
int32_t p06;
int32_t p07;
int32_t p08;
}
然后对所有节点设置唯一约束。
现在,在任何人谈论消息 ID 的唯一性质量之前,我知道,但是这个系统的设计并不是为了具有高完整性,而只是为了高性能。
所以我的问题是这样的:
这是否足够,或者有什么技巧可以在 MySql 中索引我错过的字符串?
编辑:添加模型设计。
MessageIdentity.php
/**
* MessageIdentity
*
* @ORM\Table(name="inbox_message_identity",
* uniqueConstraints={
* @ORM\UniqueConstraint(columns={
* "p01", "p02", "p03", "p04",
* "p05", "p06", "p07", "p08"
* })
* })
* @ORM\Entity(repositoryClass="AppBundle\Entity\Inbox\MessageIdentityRepository")
*/
class MessageIdentity
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="originalMessageId", type="string", length=255)
*/
private $originalMessageId;
/**
* @var integer
*
* @ORM\Column(name="p01", type="integer")
*/
private $p01;
/**
* @var integer
*
* @ORM\Column(name="p02", type="integer")
*/
private $p02;
/**
* @var integer
*
* @ORM\Column(name="p03", type="integer")
*/
private $p03;
/**
* @var integer
*
* @ORM\Column(name="p04", type="integer")
*/
private $p04;
/**
* @var integer
*
* @ORM\Column(name="p05", type="integer")
*/
private $p05;
/**
* @var integer
*
* @ORM\Column(name="p06", type="integer")
*/
private $p06;
/**
* @var integer
*
* @ORM\Column(name="p07", type="integer")
*/
private $p07;
/**
* @var integer
*
* @ORM\Column(name="p08", type="integer")
*/
private $p08;
/**
* @param $string
*/
public function __construct($string)
{
parent::__construct();
$bits = self::createBits($this->originalMessageId = $string);
$this->p01 = $bits[0];
$this->p02 = $bits[1];
$this->p03 = $bits[2];
$this->p04 = $bits[3];
$this->p05 = $bits[4];
$this->p06 = $bits[5];
$this->p07 = $bits[6];
$this->p08 = $bits[7];
}
public static function createBits($string)
{
$hash = hash('sha256', $string);
$bits = array();
// Bits are packed in pairs of 16 bit chunks before unpacking as signed 32 bit chunks
// in order to guarrentee there is no overflow when converting the unsigned hex number into a
// PHP integer on 32 bit machines.
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 0, 4))) . pack('s', hexdec(substr($hash, 4, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 8, 4))) . pack('s', hexdec(substr($hash, 12, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 16, 4))) . pack('s', hexdec(substr($hash, 20, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 24, 4))) . pack('s', hexdec(substr($hash, 28, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 32, 4))) . pack('s', hexdec(substr($hash, 36, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 40, 4))) . pack('s', hexdec(substr($hash, 44, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 48, 4))) . pack('s', hexdec(substr($hash, 52, 4)))));
$bits[] = self::pluck(unpack('l', pack('s', hexdec(substr($hash, 56, 4))) . pack('s', hexdec(substr($hash, 60, 4)))));
return $bits;
}
protected static function pluck($array)
{
return $array[1];
}
}
MessageIdentityRepository.php
class MessageIdentityRepository extends \Doctrine\ORM\EntityRepository
{
public function getExisting($string)
{
$bits = MessageIdentity::createBits($string);
$qb = $this->createQueryBuilder('i');
$qb
->where($qb->expr()->andX(
$qb->expr()->eq('i.p01', $qb->expr()->literal($bits[0])),
$qb->expr()->eq('i.p02', $qb->expr()->literal($bits[1])),
$qb->expr()->eq('i.p03', $qb->expr()->literal($bits[2])),
$qb->expr()->eq('i.p04', $qb->expr()->literal($bits[3])),
$qb->expr()->eq('i.p05', $qb->expr()->literal($bits[4])),
$qb->expr()->eq('i.p06', $qb->expr()->literal($bits[5])),
$qb->expr()->eq('i.p07', $qb->expr()->literal($bits[6])),
$qb->expr()->eq('i.p08', $qb->expr()->literal($bits[7]))
))
->setMaxResults(1)
;
return $qb->getQuery()->getOneOrNullResult();
}
}
MessageRepository.php
class MessageRepository extends \Doctrine\ORM\EntityRepository
{
public function getLastWithMessageID(MessageIdentity $messageIdentity)
{
$qb = $this->createQueryBuilder('m');
$qb
->where('m.messageIdentity = :identity')
->setParameter(':identity', $messageIdentity)
->orderBy('m.date', 'DESC')
->setMaxResults(1)
;
return $qb->getQuery()->getOneOrNullResult();
}
}
这是一个使用 Doctrine2 构建的模型。消息本身包含MessageIdentity 表的外键。
MessageIdentity 是通过重构位集来搜索的,并搜索所有应该完美利用放置在表上的唯一约束的列。
根据映射的身份搜索消息,按日期降序排列,仅提取一行。
【问题讨论】:
-
所以你有一张桌子
messages什么的?显示结构有助于回答您的问题。 -
你最终会发生碰撞。您会注意到,任何加密方法都有其自身的中度到极端性能下降。您是否表明使用 smtp ID 无法扩展?
-
使字符串在数据库中唯一的问题通常可以通过您使用它的方式来解决 - 创建哈希,存储哈希,使其唯一。
-
@Drew 没关系,因为在将新消息匹配到另一个时,我只会选择最近匹配的。它是一个可用性工具。为了防止意外重复,我将使用消息 ID,加上其他一些字段来“猜测”它是否真的是唯一的。
-
对不起,如果我误导了你 - 不,没有任何原生的东西,我只是想确认你所做的一切正常。您可以使用
UNHEX()MySQL 方法将哈希存储在二进制列中,这样您就可以减少存储需求(不需要 varchars)。
标签: php mysql string performance indexing