一. redis数据转存到mysql数据库的意义

redis的最佳使用场景是缓存,用来持久化数据存储也是可以的,但既然有更适合于用作持久化数据存储的数据库,我们为什么不用呢,做到物尽其用,发挥其最大优势,所以redis中有的数据,也有必要在mysql中进行存储。

二. redis数据如何转存到mysql数据库

思路:我们可以使用Python语言来编写一个程序,连接redis数据库,连接mysql数据库,再从redis里面读出数据,将数据插入到mysql数据库中,最后新建一个shell文件,在文件里面调用写好的Python程序即可。详细操作步骤如下:

1. 导包

import json   # json是一种轻量级的数据交换格式,后续程序中会用到json.loads()将已编码的json字符串解码为Python对象,json.dumps()是将Python对象编码成json字符串  
import redis
import pymysql

import json   # json是一种轻量级的数据交换格式,后续程序中会用到json.loads()将已编码的json字符串解码为Python对象,json.dumps()是将Python对象编码成json字符串  
import redis
import pymysql

2. 定义主函数

def main():
    # 指定redis数据库
    rediscli = redis.StrictRedis(host='IP‘, port=PORT, password='PASSWORD', db=0)
    # 指定mysql数据库
    mysqlcli = pymysql.connect(host="localhost", port=3306, user="USER", passwd="PASSWORD", db="task", charset="utf8")
    # 指定unique_id为唯一索引
    point_unique = 'alter table tb_stop_record add unique key(`unique_id`)'
    mysqlcli.cursor().execute(point_unique)
    # 设置lrange取值的起始值和终止值,步长,页数初始值设为0
    page_start = 0
    step = 2
    page_end = page_start + step
    page_num = 0
    # 统计出页数
    while True:
        data = rediscli.lrange('acct_stop_record', start=page_start, end=page_end)
        # 判断获取到的数据是否为空
        if data:
            page_start = page_end + 1
            page_end = page_start + step
            page_num += 1
        # 如果获取到的数据为空,则跳出循环
        else:
            break
    # 重新设置lrange取值的起始值和终止值,步长,数据总数初始值设为0,实际插入的数据条数初始值为0
    page_start = 0
    step = 2
    page_end = page_start + step
    total = 0
    count = 0
    for page in range(0, page_num):
        data_list = rediscli.lrange('acct_stop_record', start=page_start, end=page_end)
        page_start = page_end + 1
        page_end = page_start + step
        params = []
        for data in data_list:
            total += 1
            item = json.loads(data)
            if item.get('session_id'):
                params.append(([item.get('session_id'), item.get('unique_id'), 			item.get('member_name')]))
            else:
                continue
        try:
            # 使用cursor()方法获取游标
            cursor = mysqlcli.cursor()
            sql = "insert ignore into tb_stop_record(session_id,unique_id,member_name) " \
                  "VALUES(%s,%s,%s)"
            # 添加的数据量
            add_num = cursor.executemany(sql, params)
            count = count + add_num
            mysqlcli.commit()
            # 关闭游标
            cursor.close()
        except pymysql.Error as e:
            mysqlcli.rollback()
            print "insert error", e
    try:
        # 根据实际插入的条数和每页的条数,计算出页数,用math.ceil()向上取整
        if count % (step + 1) == 0:
            page_num = count / (step + 1)
        else:
            page_num = math.ceil(count / (step + 1))
        # 计算最后一页实际插入的条数
        if count % int(page_num) == 0:
            last_page_data_num = step + 1
        else:
            last_page_data_num = count % int(step + 1)
        print 'There are {} datas'.format(total)
        print 'Actually add {} pages,insert {} datas,per page has {} datas,the last page insert                {} datas.'.format(int(page_num), count,step + 1, last_page_data_num)
    except ZeroDivisionError:
        print 'There are {} datas'.format(total)
        print 'The data already exists in the database'

def main():
    # 指定redis数据库
    rediscli = redis.StrictRedis(host='IP‘, port=PORT, password='PASSWORD', db=0)
    # 指定mysql数据库
    mysqlcli = pymysql.connect(host="localhost", port=3306, user="USER", passwd="PASSWORD", db="task", charset="utf8")
    # 指定unique_id为唯一索引
    point_unique = 'alter table tb_stop_record add unique key(`unique_id`)'
    mysqlcli.cursor().execute(point_unique)
    # 设置lrange取值的起始值和终止值,步长,页数初始值设为0
    page_start = 0
    step = 2
    page_end = page_start + step
    page_num = 0
    # 统计出页数
    while True:
        data = rediscli.lrange('acct_stop_record', start=page_start, end=page_end)
        # 判断获取到的数据是否为空
        if data:
            page_start = page_end + 1
            page_end = page_start + step
            page_num += 1
        # 如果获取到的数据为空,则跳出循环
        else:
            break
    # 重新设置lrange取值的起始值和终止值,步长,数据总数初始值设为0,实际插入的数据条数初始值为0
    page_start = 0
    step = 2
    page_end = page_start + step
    total = 0
    count = 0
    for page in range(0, page_num):
        data_list = rediscli.lrange('acct_stop_record', start=page_start, end=page_end)
        page_start = page_end + 1
        page_end = page_start + step
        params = []
        for data in data_list:
            total += 1
            item = json.loads(data)
            if item.get('session_id'):
                params.append(([item.get('session_id'), item.get('unique_id'), 			item.get('member_name')]))
            else:
                continue
        try:
            # 使用cursor()方法获取游标
            cursor = mysqlcli.cursor()
            sql = "insert ignore into tb_stop_record(session_id,unique_id,member_name) " \
                  "VALUES(%s,%s,%s)"
            # 添加的数据量
            add_num = cursor.executemany(sql, params)
            count = count + add_num
            mysqlcli.commit()
            # 关闭游标
            cursor.close()
        except pymysql.Error as e:
            mysqlcli.rollback()
            print "insert error", e
    try:
        # 根据实际插入的条数和每页的条数,计算出页数,用math.ceil()向上取整
        if count % (step + 1) == 0:
            page_num = count / (step + 1)
        else:
            page_num = math.ceil(count / (step + 1))
        # 计算最后一页实际插入的条数
        if count % int(page_num) == 0:
            last_page_data_num = step + 1
        else:
            last_page_data_num = count % int(step + 1)
        print 'There are {} datas'.format(total)
        print 'Actually add {} pages,insert {} datas,per page has {} datas,the last page insert                {} datas.'.format(int(page_num), count,step + 1, last_page_data_num)
    except ZeroDivisionError:
        print 'There are {} datas'.format(total)
        print 'The data already exists in the database'

3. 执行

"""
一个python的文件有两种使用的方法,第一是直接作为脚本执行,第二是import到其他的python脚本中被调用(模块重用)执行。因此if __name__ == '__main__': 的作用就是控制这两种情况执行代码的过程,在if __name__ == '__main__': 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而import到其他脚本中是不会被执行的。
"""
if __name__ == "__main__":
    main()

"""
一个python的文件有两种使用的方法,第一是直接作为脚本执行,第二是import到其他的python脚本中被调用(模块重用)执行。因此if __name__ == '__main__': 的作用就是控制这两种情况执行代码的过程,在if __name__ == '__main__': 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而import到其他脚本中是不会被执行的。
"""
if __name__ == "__main__":
    main()

4. 创建shell文件并调用Python文件

在终端命令行中输入以下命令,创建一个shell脚本

vim redis_mysql_script.sh

进入后开启编辑模式,输入一下代码:

#! /bin/bash
python filename.py

退出编辑模式,保存并退出,在命令行中执行shell脚本:

sh redis_mysql_script.sh

数据转存成功。

三. 总结

这一过程看似挺简单,实际上要注意的点不少,例如从redis数据库中取出数据时如果用blpop或者lpop的话,会将原库中的数据删除,而一旦数据在mysql中没插入成功,那么我们的数据就丢失了。如果我们使用lrange方法来读取数据,能保证数据的安全性。sql语句中占位符也是一个值得注意的地方,不管是什么类型,统一用%s作为占位符。还有redis读取出的数据字段的个数和mysql表中的字段个数不相等的问题,也需要注意。还有效率问题,如果用for循环来遍历这个列表,那么在列表容量大的情况下,效率会很低,因此,需要用批量来读取,批量插入mysql数据库。还有要解决对重复数据插入的解决。