<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; 位存储</title>
	<atom:link href="https://www.phppan.com/tag/%e4%bd%8d%e5%ad%98%e5%82%a8/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sat, 25 Apr 2026 00:56:17 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>用户参与记录存储的演变</title>
		<link>https://www.phppan.com/2013/07/user-operation-record/</link>
		<comments>https://www.phppan.com/2013/07/user-operation-record/#comments</comments>
		<pubDate>Sun, 14 Jul 2013 08:35:39 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[程序相关]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[位存储]]></category>
		<category><![CDATA[分库]]></category>
		<category><![CDATA[分表]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1847</guid>
		<description><![CDATA[有这样一个应用场景：用户有两个连续的操作A和操作B，必须是操作A完成后才能执行操作B，如果操作A没有完成就触发 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>有这样一个应用场景：用户有两个连续的操作A和操作B，必须是操作A完成后才能执行操作B，如果操作A没有完成就触发了操作B，则显示用户需要先执行操作A，即在操作B执行需要查询操作A是否执行过。这里引申出来的问题是，记录用户参与记录，提供针对用户和操作的查询方法。当不同的数据量时，我们的存储方案会大不相同，随着数据的增长，方案不断演变。</p>
<p><strong>1、数据量较小，用户操作行为固定：</strong><br />
     存储：MySQL<br />
     方案：我们以UID为key，一行一个用户，每个用户包括的用户作为列存储，比如UID=100，固定存储为操作A和操作B，则表结构大致如下：<br />
     table_operation<br />
     uid   operation_a  operation_b<br />
     100   1             1      </p>
<p>如果我们要查询用户是否参与A或B时，直接使用SQL: SELECT * FROM table_operation WHERE uid=100  AND  action_a=1就可以达成目标。</p>
<p>    问题：用户操作固定，扩展较难，如果需要增加用户操作行为，则需要增加字段或增加表存储，增加字段的方法在一定的数据量级以下（比如100万）是可行的，如果行为间无关，则增加表存储方案的表现会很不错。</p>
<p><strong>2、数据量较小、用户操作行为不固定：</strong><br />
     与场景1相比，当前场景除了uid这个变量，增加了用户操作变量，即我们需要关注用户和用户操作两个变量。<br />
     存储：MySQL<br />
     方案1：增加操作表，生成操作id，用户操作行为表存储uid和oid。当用户执行一个新的操作时就在操作行为表插入一条记录。其表结构大致如下：</p>
<p>      table_operation_info<br />
      oid    name<br />
      1       operation_a<br />
      2       operation_b</p>
<p>      table_operation<br />
      uid   oid<br />
      1      1<br />
      1      2</p>
<p>     当需要查询用户1是否执行过操作A时，使用SQL：SELECT * FROM table_operation WHERE oid=1 AND  oid=1。<br />
      问题：当用户的操作行为较多时，用户操作行为增长速度很快，数据量也为逐渐增大，可能MySQL单表无法负载。解决方案在后续场景中说明。</p>
<p><strong>3、数据量较大，用户行为固定</strong><br />
       存储：MySQL<br />
       方案：与场景1相比，当前场景不同在于数据量比场景1大，数据量大到MySQL单表负载不过来。此方案解决的就是这个问题，当单表太大时，性价比较高的方法一般是采用分表。我们当前场景的变量是uid，只要依据uid按水平分表即可。</p>
<p><strong>4、数据量较大，用户行为不固定</strong><br />
      存储： MySQL<br />
      方案1：此方案应用于用户的操作行为可以分类的情况，即在场景1的基础上增加两次分表操作，按操作行为类分表和按用户分表。当前方案中我们需要应对两个变量：操作行为和用户。两次分表分别对应这两个变量，按业务规则做操作行为的分表操作，按用户id水平切分减少数据量。</p>
<p>       方案2：此方案是完全的水平分表操作，在场景2的方案基础上，按用户水平切分。</p>
<p><strong>5、数据量超大</strong><br />
     存储： MySQL<br />
     方案1：分库分表，此时一个库已经无法满足需求，规则依据前面的场景实现，根据实际的需求可以考虑把不同的库放不同的机器上。<br />
     方案2：在分库分表的基础上，按位存储，因为一个操作行为有没有执行过是一个是否的状态，即0，1状态，因此我们可以用一个位来存储，64位可以存储64个操作行为的标记。</p>
<p><strong>其它存储</strong><br />
     key-value数据库<br />
     我们的需求实际上并不需要太多的关系型数据库的功能，简单的 k-v数据库就可以实现我们的功能，并且在性能上也会有所提升，毕竟做得少，会快。<br />
     先不管是选择基于内存的，还是非内存的（可以根据实际需求来选择，也可以是热点数据在内存，沉默数据在非内存中），假设我们有足够的空间存储。<br />
     方案1：<br />
        以uid+oid为key，值可以存储状态，也可以只存储是否参与（0和1），但是会存在key太多的情况，特别是当数据量超大时，uid的个数*oid的个数，可能是你无法相像的量级。<br />
     方案2：<br />
        一般来说，用户操作行为的数据量完全小于用户的量级，并且用户操作行为的数据可控。如果要减少key的个数，我们可以使用oid+用户分区索引id作为key，这里所谓的用户分区索引是指将用户以某个数量分成一个区，所有的用户都记录在这个这个区间内，比如以10000为一个区间，则uid为1到9999的用户分到区间0，这里可以以1和0存储用户是否执行了此操作，一个key对应的value初始化存储10000个0。当uid=100的用户执行了某操作，则将第100个0置为1。<br />
      方案3：<br />
           在方案2的基础上，将10000个0转换为10000个01位，假设一个位存储50位，则总共只需要200个。<br />
      方案4：<br />
         当用户量超大时，大多数的用户对于某个操作可能都是没有参与的，则在方案3的基础上我们增加简单的稀疏矩阵压缩，给每个存储位添加索引，当存储值不为0时才会存储。<br />
     方案5：<br />
        我还没想到，期待你的分享</p>
<p><strong>小结</strong></p>
<ul>
<li>随着数据量的增大，总的思路是分冶，当一个表搞不定时分表，当一个库搞不定时分库，当一台机器搞不定时加机器。</li>
<li>对于不同的存储介质选择需要考虑成本和需求，所有的选择都是平衡后的结果。</li>
<li>节省空间，按位存储。</li>
<li>不要过早优化。</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2013/07/user-operation-record/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
