mysql offset 为什么这么慢。。。

之前从来没觉得 offset 有什么坑,也没有细想过 mysql 的 offset 的实现原理。

直到这周打算把 4000w+ 的数据热到 redis 中,写了一个脚本, 主要的代码大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from redis import Redis
from sqlalchemy import select
import table # Mysql Table Object
redis_cli = Redis(xxxxxxxxxxx)
CUR = 0
MAX = 40000000
while CUR <= MAX:
query = text("""SELECT id FROM example
LIMIT CUR, 1000
ORDER BY id DESC""")
result = table.execute(query).fetchall()
pipe = redis_cli.pipeline() # 使用 pipeline 来减少连接开销
for item in result:
pipe.set(item.id, 'foo')
pipe.execute()
CUR += 1000

开始执行大概下午 6 点左右,然后我就去吃饭逗猫写代码又睡了一觉。

上午 11 点左右来公司发现,才完成了 1000w 左右的数据,内心是崩溃的。。。。。

看了一眼 slow log,一次 Mysql 的查询需要 40s, 然后开始查一些资料找原因,发现 offset/limit 根本无法用到 index 机制,而是读整张表,然后数到需要便宜的位置,所以上面的代码到 1000w 时, mysql 会按照 id 的顺序逐条累加,一直找到第 1000w 的位置(至于为什么不通过 index 来直接找到 id 为 10000000 的数据,原因很简单,id 为 10000000 的数据并不已经代表是第 1000w 条数据,中间有可能会有数据被删除使得 id 非连续)。

找到了原因重写了一把脚本,把 offset 改成 where 就解决了这个问题,然后用了半个小时就跑完了数据- -。

thumbnail

Python 中的「全局变量」的小细节

前几天被同学问了一个问题,为什么自己修改修改了全局变量但是没有生效,例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File: foo.py
l = 10
def bar():
global l
l = 20
#-----------------------------
# File: main.py
from foo import l, bar
if __name__ == '__main__':
print l
bar()
print l
输出:
10
10

所以我们还是要弄明白什么是全局变量。。。

Python 有全局变量么

有 - -|||。。。。。。
有两种情况是全局变量:

  • 在当前文件中的最外层作用于声明的变量为全局变量
  • 用 global 声明的变量为全局变量

Python 的「全局变量」的作用域是多大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# foo.py
l = 10
def bar():
global b
b = 1
--------------------------
# In ipython
In [1]: import foo
In [2]: 'l' in foo.__dict__
Out[2]: True
In [3]: 'b' in foo.__dict__
Out[3]: False
In [4]: foo.bar()
In [5]: 'b' in foo.__dict__
Out[5]: True

从上面的代码里我们得到以下几个结论

  • 全局变量的作用域被绑定到所在文件之下
  • 即使是先加载再声明全局变量,依然会绑定到该文件之中

为什么我不能跨文件修改全局变量

其实,正确的来说,这个问题问的并没有切中要点,在最初的代码中,我们无法修改 l 的主要原因是 import 的特性导致

The from form does not bind the module name: it goes through the list of identifiers, looks each one of them up in the module found in step (1), and binds the name in the local namespace to the object thus found.

所以当你执行这个语句

1
from foo import l

本质是在当前的 Namespace 中声明了变量 l, 并将 l 指向 foo 这个 module 中的 l 所指向的对象。

当你执行 foo.bar() 的时候,将 foo 中的 l 改为了 20, 但是 main.py 中 l 扔指向 10,所以并没有实现夸文件修改.
同理,在 main.py 中修改 l 也不会影响 foo.l

最后的最后

虽然前面举了例子,有分析了原理,但是其实就像是聊屠龙术的运行原理(更何况「全局变量」连屠龙书都算不上,顶多算是这个图里的 IE

除非是用来定义常量,否则不要在 Python 里用全局变量。

运营商缓存导致的奇葩问题

很久之前遇到的一个问题, 趁还记得, 记录下来, 避免日后忘记.

在公司上线 2014特别项目 之后, 有用户反馈出现了穿好问题. 这个这问题之前从来没有出现过.

通过检查服务器端未出现 Session 冲突, 用户在主站访问正常, 但是在进入 2014 项目之后, 发现信息出现串号现象.

我们的用户信息的 API 请求地址的格式类似为

1
/user_data.json

所有用户都会请求这个地址, 然后由服务器动态生成 Response 并返回, 通过在线上检查发现返回的数据也没有错误.

后来又发现, 遇到的串号用户基本上集中在相同的几个地区. 通过这个线索, 发现问题是出在 ISP服务商 的环节, 有些小的运营商会对你的静态数据做 cache 来加快用户访问速度, 即使你加了 no-cache 的设置, 但是仍然会有些运营商不按照规范强行缓存.

临时的解决方法就是讲请求地址改为

1
/user_data.json?_=[unix_timestamp]

利用 UNIX时间戳 来避开运营商缓存的问题, 最终问题得以解决.

作为总结, 在以后设计 API 的时候尽量要将每个用户的请求地址设计为独一无二的地址, 避免服务器对同一地址动态生成不同的数据, 最终避开这个问题.

PS:
想对运营商说….Fuck!

Amazon S3 的坑

之前就遇到过一次, 今天又有同事遇到, 总之 boto 是个很神奇的项目, 超多 issue 而且跟着文档走 S3 基本不可用, 经常会遇到 400 错误 T_T. 所以单独记录下.

正确的连接 boto:
首先在目录下创建 .boto 文件, 写入 access key 和 secret key:

1
2
3
[Credentials]
aws_access_key_id = YOURID
aws_secret_access_key = YOURKEY

然后连接的时候要注意设置 validate 为 False:

1
2
3
4
import boto.s3
from boto.s3.connection import Location
c = boto.s3.connect_to_region(Location.CNNorth1)
c.get_bucket(BUCKET_NAME, validate=False)