面試題:如何造10w條測試數據,在數據庫插入10w條不同數據


前言

面試題:如果造10w條測試數據,如何在數據庫插入10w條數據,數據不重復

最近面試經常會問到sql相關的問題,在數據庫中造測試數據是平常工作中經常會用到的場景,一般做壓力測試,性能測試也需在數據庫中先准備測試數據。那么如何批量生成大量的測試數據呢?

由於平常用python較多,所以想到用python先生成sql,再執行sql往數據庫插入數據。

使用語言:python 3.6

插入數據

首先我要插入的 SQL 語句,需每條 id 不重復 ,下面是執行單個插入語句

INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('1', '', 'test123', '2019-12-17');

10w 太多執行時間長,用 python 先生成 1w條測下執行時間。

首先要生成多個insert 語句,這里我用 python 語言寫段生成sql的腳本。

  • 用 %s 替換需要變的字段值,如果有多個值都需要變,可以用多個%s替換對應值,我這里設計的表,只要id不一樣就可以插入成功。

  • 用for 循環,每次循環 id 加1,這樣 id 就可以保證不會重復,否則插入數據庫時有重復的無法寫入成功。

  • a 是追加寫入

  • 每條sql后面分號隔開

  • 每次寫入數據,最后面加\n 換行

# python3
# 作者:上海-悠悠

for i in range(10000):
    a = "INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('%s', '', 'test123', '2019-12-17');"%str(i+1)
    with open("a.txt", "a") as fp:
        fp.write(a+"\n")

執行python代碼,在本地生成一個 a.text 文件,打開生成的數據,部分如下

INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('1', '', 'test123', '2019-12-17');
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('2', '', 'test123', '2019-12-17');
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('3', '', 'test123', '2019-12-17');
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('4', '', 'test123', '2019-12-17');
......
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('10000', '', 'test123', '2019-12-17');

如果id是手機號呢,如何生成10w個不同手機號?

可以按手機號前3位開頭的號碼段生成,比如186開頭的,先用初始數據 1860000000,再這個數字基礎上每次加1

加到 18600099999,這樣號碼段1860000000-18600099999就是10w個手機號了。

把id換成手機號后,修改代碼如下

# python3
# 作者:上海-悠悠

for i in range(10000):
    a = "INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('%s', '', 'test123', '2019-12-17');"%str(i+1860000000)
    with open("a.txt", "a") as fp:
        fp.write(a+"\n")

只需在上面基礎上把 str(i+1) 改成 str(i+1860000000) 就可以生成手機號了

INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('1860000000', '', 'test123', '2019-12-17');
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('1860000001', '', 'test123', '2019-12-17');
INSERT INTO `apps`.`apiapp_card` (`id`, `card_id`, `card_user`, `add_time`) VALUES ('1860000002', '', 'test123', '2019-12-17');

把生成的文本復制出來 ,多個INSERT INTO 對應的 sql 一次性貼到 navicat 客戶端執行

1.png

執行完成花了5分鍾左右,也就是說10w條得50分鍾,這太慢了,要是數據更多,會等太久,不是我們想要的效果!

批量執行

由於單個執行,花費時間太長,現在需要優化下改成一個 inert 語句,改成批量插入數據,只寫一個 insert into 這樣一次性批量寫到數據庫,會快很多。

可以將SQL語句進行拼接,使用 insert into table () values (),(),(),()然后再一次性插入。

批量執行要么全部成功,要么一個都不會寫入成功,當寫的 SQL 語法有問題時就不會寫入成功了。

需注意:

  • 拼接 sql ,多個values 值中間用英文逗號隔開

  • value 值要與數據表的字段一一對應

  • 一定要注意最后一條數據后面不是逗號,改成分號

# python3
# 作者:上海-悠悠

insert_sql = "INSERT INTO `apps`.`apiapp_card`  VALUES "
with open("b.txt", "a") as fp:
        fp.write(insert_sql+"\n")
for i in range(10000):
    a = "('%s', '', 'test123', '2019-12-17'),"%str(i+10001)
    with open("b.txt", "a") as fp:
        fp.write(a+"\n")

執行完成后,復制 b.text 文件的內容,需注意的是這里一定要改成 ;結尾,否則語法報錯

部分數據內容展示如下

INSERT INTO `apps`.`apiapp_card` VALUES 
('10001', '', 'test123', '2019-12-17'),
('10002', '', 'test123', '2019-12-17'),
......
('20000', '', 'test123', '2019-12-17');

復制生成的 INSERT INTO 到 navicat 客戶端執行

2.png

執行完成,最后看的測試結果,1w條數據只用了0.217秒,速度明顯提高不少。

10w數據插入

接着測下,當生成10 w條數據的時候,會花多少時間?

# 作者:上海-悠悠
# python3

insert_sql = "INSERT INTO `apps`.`apiapp_card`  VALUES "
with open("b.txt", "a") as fp:
        fp.write(insert_sql+"\n")
for i in range(100000):
    a = "('%s', '', 'test123', '2019-12-17'),"%str(i+100000)
    with open("b.txt", "a") as fp:
        fp.write(a+"\n")

使用python腳本執行后生成的數據如下

INSERT INTO `apps`.`apiapp_card`  VALUES 
('100000', '', 'test123', '2019-12-17'),
('100001', '', 'test123', '2019-12-17'),
......
('199999', '', 'test123', '2019-12-17');

直接插入mysql 這時候會有報錯:Err 1153 - Got a packet bigger than 'max_allowed_packet' bytes

4.png

報錯原因:由於數據量較大,mysql 會對單表數據量較大的 SQL 做限制,10w條數據的字符串超出了max_allowed_packet

的允許范圍。

解決辦法:需修改mysql 數據庫的max_allowed_packet的值,改大一點

max_allowed_packet

先在 navicat 輸入命令查看 max_allowed_packet 最大允許包

show global variables like 'max_allowed_packet';

5.png

查看到 value 值是 4194304, 最大限制是 40 M,我們只需的sql字符串太大了,超出了這個范圍。

在 navicat 客戶端我們無法直接修改對應 value值,需登錄到mysql,用命令行修改。

我這里 mysql 是搭建在 docker 上,需先進容器,登錄到mysql.

操作步驟如下:

  • docker exec 進docker容器
  • mysql -uroot -p 輸入密碼后登錄mysql
  • set global max_allowed_packet=419430400; 設置最大允許包 400M
  • show global variables like 'max_allowed_packet'; 查看前面設置是否生效
[root@VM_0_2_centos ~]# docker exec -it 934b30a6dc36 /bin/bash
root@934b30a6dc36:/# mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 303822
Server version: 5.7.27 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show global variables like 'max_allowed_packet';
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| max_allowed_packet | 4194304 |
+--------------------+-----------+
1 row in set (0.00 sec)

mysql> set global max_allowed_packet=419430400;
Query OK, 0 rows affected (0.00 sec)

mysql> show global variables like 'max_allowed_packet';
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| max_allowed_packet | 419430400 |
+--------------------+-----------+
1 row in set (0.00 sec)

mysql> 

從上面的查詢結果可以看到,已經生效了。

再次重新執行上面10w條數據,查看運行結果總共花11秒左右時間。

受影響的行: 100000

時間: 11.678s

6.png

上面的方法只能臨時生效,當重啟mysql后,你會發現又還原回去了。

這里還有一種永久生效的方法,需修改my.cnf配置文件

在[mysqld]部分添加一句,如果有就修改對應的值:

- max_allowed_packet=40M

這里的值,可以用 M單位,修改后,需要重啟下mysql就可以生效了

使用python執行

如果不用 navicat 客戶端,直接用python去執行,會花多少時間呢?

先封裝連接mysql的方法,然后拼接執行的sql語句,拼接的時候需注意,最后的字符 ,需改成 ;

在執行代碼前先獲取當前的時間戳,代碼執行完成后再次獲取一次時間戳。兩次的時間間隔,就是執行的時間了,時間單位是s

python 執行 mysql 代碼參考如下

import pymysql
'''
# python3
作者:上海-悠悠
pip install PyMySQL==0.9.3
'''

dbinfo = {
    "host": "192.168.1.x",
    "user": "root",
    "password": "123456",
    "port": 3306}


class DbConnect():
    def __init__(self, db_cof, database=""):
        self.db_cof = db_cof
        # 打開數據庫連接
        self.db = pymysql.connect(database=database,
                                  cursorclass=pymysql.cursors.DictCursor,
                                  **db_cof)

        # 使用cursor()方法獲取操作游標
        self.cursor = self.db.cursor()

    def select(self, sql):
        # SQL 查詢語句
        # sql = "SELECT * FROM EMPLOYEE \
        #        WHERE INCOME > %s" % (1000)
        self.cursor.execute(sql)
        results = self.cursor.fetchall()
        return results

    def execute(self, sql):
        # SQL 刪除、提交、修改語句
        # sql = "DELETE FROM EMPLOYEE WHERE AGE > %s" % (20)
        try:
           # 執行SQL語句
           self.cursor.execute(sql)
           # 提交修改
           self.db.commit()
        except:
           # 發生錯誤時回滾
           self.db.rollback()

    def close(self):
        # 關閉連接
        self.db.close()


if __name__ == '__main__':
    import time
    insert_sql = "INSERT INTO `apps`.`apiapp_card`  VALUES "
    insert_values = "".join(["('%s', '', 'test123', '2019-12-17'), \n"%str(i+100000) for i in range(100000)])
    # 拼接sql
    sql = insert_sql + insert_values[:-3]+";"
    # print(sql)
    # 執行sql
    time1 = time.time()
    db = DbConnect(dbinfo, database="apps")
    db.execute(sql)
    db.close()
    time2 = time.time()
    print("總過耗時:%s" % (time2-time1))

使用python執行結果:總過耗時:1.0816256999969482,結果超出我的想象,10w條數據居然只要1秒鍾!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM