Php MySQL-字符串索引
我正在寻找一种在字符串上设置唯一约束的最佳方法 我的用例是通过SMTP提要上的“邮件ID”和“回复”字段将所有电子邮件链接在一起 然而,由于邮件的数量可能会增长到数百万条,而且我们没有删除任何内容的计划,因此我需要一种快速索引它们的方法。问题是我的印象是字符串在索引这些数字时天生就比较慢(如果我错了,请解释) 到目前为止,我的解决方案是将消息ID转换为Php MySQL-字符串索引,php,mysql,string,performance,indexing,Php,Mysql,String,Performance,Indexing,我正在寻找一种在字符串上设置唯一约束的最佳方法 我的用例是通过SMTP提要上的“邮件ID”和“回复”字段将所有电子邮件链接在一起 然而,由于邮件的数量可能会增长到数百万条,而且我们没有删除任何内容的计划,因此我需要一种快速索引它们的方法。问题是我的印象是字符串在索引这些数字时天生就比较慢(如果我错了,请解释) 到目前为止,我的解决方案是将消息ID转换为sha256散列,然后转换为8 x 32位块256位数字,如下所示: // Its not actually written in C struc
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();
}
}
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();
}
}
MessageRepository.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();
}
}
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
是通过重构位集并搜索所有列来搜索的,这些列应该完全利用表上的唯一约束
根据映射的标识(按降序日期排序)搜索邮件,只提取一行。您正在解决一个不存在的问题 当然,比较两个字符串要比比较两个
int
慢一点。但是速度不够慢,不能保证用MD5/SHA1/等站在你的头上。这样做的所有开销都会比字符串的速度慢得多
另一方面,如果您计划使用长度超过767字节的唯一字符串,则需要做一些事情。如果是这样,我将讨论一些解决办法
同时,我认为SHA256是严重的过度使用。对于128位MD5,“在一个包含9万亿字符串的表中,9万亿次错误复制中有一次机会。”对于仅“数百万次”的情况,可能性更为渺茫
还有一点<代码>二进制(20)和CHAR(20)COLLATE…\u bin
的处理方式相同。更复杂的排序需要更多的努力
附录
背景:实际上MySQL中唯一的索引类型是BTree。这是一个有序的列表。它对于“点查询”(给定一个键,查找记录)也非常有效。对于InnoDB,BTree块是16KB的块。对于一百万行来说,BTree大约有3层深。一万亿——六
SHA256/UUID/GUID/其他摘要——当您有非常多的行时,所有这些都会执行得非常糟糕。这是因为它们是非常随机的,在非常大的表的情况下,您不可能将所需的块缓存在RAM中
以下是一个折衷的解决方案,以人工表为例:
CREATE TABLE foo (
in_reply_to VARCHAR(500) NOT NULL CHARACTER SET ...,
...
KEY(in_reply_to(20))
PRIMARY KEY (...)
) ENGINE=InnoDB;
SELECT ... FROM foo
WHERE in_reply_to = '...';
请注意,我没有sha256或其他摘要
KEY(在回复(20)中)
用该字段的前20个字符建立索引。UNIQUE(在对(20)的回复中)
也会这样做,但也会将20个字符限制为唯一。这不是你想要的
以下是我的SELECT
的工作原理:
是否与搜索字符串完全匹配
- 足够长,通常是独一无二的
- 短到足以使钥匙的B树保持小。(更小-->更可缓存-->更快)
SELECT
的某些变体来实现这一点
SHA256有256位,因此需要
二进制(32)
。即使使用1e25不同的文档,仍有大约1/1e25的文档存在虚假副本。您正在解决一个不存在的问题
当然,比较两个字符串要比比较两个int
慢一点。但是速度不够慢,不能保证用MD5/SHA1/等站在你的头上。这样做的所有开销都会比字符串的速度慢得多
另一方面,如果您计划使用长度超过767字节的唯一字符串,则需要做一些事情。如果是这样,我将讨论一些解决办法
同时,我认为SHA256是严重的过度使用。对于128位MD5,“在一个包含9万亿字符串的表中,9万亿次错误复制中有一次机会。”对于仅“数百万次”的情况,可能性更为渺茫
还有一点<代码>二进制(20)和CHAR(20)COLLATE…\u bin
的处理方式相同。更复杂的排序需要更多的努力
附录
背景:实际上MySQL中唯一的索引类型是BTree。这是一个有序的列表。它对于“点查询”(给定一个键,查找记录)也非常有效。对于InnoDB,BTree块是16KB的块。对于一百万行来说,BTree大约有3层深。一万亿——六
SHA256/UUID/GUID/其他摘要——当您有非常多的行时,所有这些都会执行得非常糟糕。这是因为它们是非常随机的,在非常大的表的情况下,您不可能将所需的块缓存在RAM中
以下是一个折衷的解决方案,以人工表为例:
CREATE TABLE foo (
in_reply_to VARCHAR(500) NOT NULL CHARACTER SET ...,
...
KEY(in_reply_to(20))
PRIMARY KEY (...)
) ENGINE=InnoDB;
SELECT ... FROM foo
WHERE in_reply_to = '...';
请注意,我没有sha256或其他摘要
KEY(在回复(20)中)
用该字段的前20个字符建立索引。UNIQUE(在对(20)的回复中)
也可以这样做,但也可以是const