Nodejs模拟并发,尝试的两种解决方案

 一、准备数据库表

创建商品库存表 db_stock ,插入一条数据

DROP TABLE IF EXISTS `db_stock`;
CREATE TABLE `db_stock`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品id',
  `inventory_total` int(11) NULL DEFAULT NULL COMMENT '总库存',
  `inventory_remain` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '记录实时库存表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of db_stock
-- ----------------------------
INSERT INTO `db_stock` VALUES (1, 'goods_01', 100, 100);

SET FOREIGN_KEY_CHECKS = 1;

 

创建出库信息表 db_checkout 

DROP TABLE IF EXISTS `db_checkout`;
CREATE TABLE `db_checkout`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品id',
  `opt_num` int(11) NULL DEFAULT NULL COMMENT '操作数量',
  `inventory_remain` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '出库记录表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

查看数据库

mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
|  1 | goods_01 |             100 |              100 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec)

mysql> select * from db_checkout;
Empty set (0.00 sec)

mysql>

 

二、准备Nodejs接口(基于Koa2)

  接口说明:

  url    dm/testConcurrentOrder

  method   get

  data   initDB //首次初始化数据

       goods_id //商品id

       opt_num //出库数量



router.js

const router = require('koa-router')();
const dm_model = require("../models/dm_model");

router.preffix("/dm");
/** * 测试并发 * testConcurrentOrder */ router.get('/testConcurrentOrder', async function (ctx) { let reqParams = ctx.reqParams; try { ctx.body = await dm_model.testConcurrentOrder(reqParams); } catch (e) { ctx.body = { success: false, msg: `出库异常: ${e.toString()}`, } } }); module.exports = router;

 model.js

let db = require("../utils/mysql_util").db;
let moment = require("moment"); let model = {}; /** * 测试并发 * @param reqParams * @returns {Promise<{msg: string, success: boolean}>} */ model.testConcurrentOrder = async function (reqParams) { let initDB = !!reqParams.initDB || 0;// 是否初始化db let goods_id = reqParams.goods_id || "goods_01";// 商品id let opt_num = reqParams.opt_num || 1;// 出库数量 if (initDB) { // 清除数据 await db.query(`TRUNCATE db_stock`); await db.query(`TRUNCATE db_checkout`); // 插入库存数据 await db.query(`INSERT INTO db_stock(goods_id, inventory_total, inventory_remain) VALUES ('${goods_id}', 100, 100)`); return { success: true, msg: '初始化DB成功', } } else { let tran = await db.beginTransaction();
     // 查询剩余库存
        let querySql = `SELECT
    t.inventory_total,
    t.inventory_remain 
FROM
    db_stock t 
WHERE
    1 = 1 
    AND t.goods_id = ?`;

        try {
            let result = {};

            let queryResult = await tran.queryOne(querySql, [goods_id]);
            if (!queryResult) {
                result = {
                    success: false,
                    msg: `无法匹配商品id:${goods_id}`
                }
            } else {
                let inventory_remain = queryResult.inventory_remain - 1;

                // 新增出库记录
                let checkoutSql = `INSERT INTO db_checkout(goods_id, opt_num, inventory_remain, create_time) VALUES (?, ?, ?, ?)`;
                await tran.query(checkoutSql, [goods_id, opt_num, inventory_remain, new Date()]);

                // 更新库存信息
                let updateStockSql = `UPDATE db_stock t SET inventory_remain = ? WHERE t.goods_id = ?`;
                // 更新库存
                await tran.query(updateStockSql, [inventory_remain, goods_id]);
                result = {
                    success: true,
                    msg: '出库成功',
                }
            }
            await tran.commit();
            return result;
        } catch (e) {
            await tran.rollback();
            throw e;
        }
    }

};

module.exports = model;

 三、并发测试

总共请求20次,20个并发同时请求

C:\WINDOWS\system32>ab.exe -n 20 -c 20  http://localhost:3000/dm/testConcurrentOrder
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:
Server Hostname:        localhost
Server Port:            3000

Document Path:          /dm/testConcurrentOrder
Document Length:        228 bytes

Concurrency Level:      20
Time taken for tests:   24.284 seconds
Complete requests:      20
Failed requests:        0
Total transferred:      7420 bytes
HTML transferred:       4560 bytes
Requests per second:    0.82 [#/sec] (mean)
Time per request:       24284.077 [ms] (mean)
Time per request:       1214.204 [ms] (mean, across all concurrent requests)
Transfer rate:          0.30 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       1
Processing:  1590 2674 4696.9   1638   22629
Waiting:     1589 2673 4696.2   1637   22625
Total:       1590 2674 4696.9   1638   22629

Percentage of the requests served within a certain time (ms)
  50%   1638
  66%   1644
  75%   1648
  80%   1651
  90%   1655
  95%  22629
  98%  22629
  99%  22629
 100%  22629 (longest request)

C:\WINDOWS\system32>

查看并发请求后,数据库表的信息

mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
|  1 | goods_01  |             100 |               97 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec)

mysql> select * from db_checkout;
+----+----------+---------+------------------+---------------------+
| id | goods_id | opt_num | inventory_remain | create_time         |
+----+----------+---------+------------------+---------------------+
| 61 | goods_01  |       1 |               99 | 2019-10-09 17:31:01 |
| 62 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 63 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 64 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 65 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 66 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 67 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 68 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 69 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 70 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 71 | goods_01  |       1 |               98 | 2019-10-09 17:31:01 |
| 72 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 73 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 74 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 75 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 76 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 77 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 78 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 79 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
| 80 | goods_01  |       1 |               97 | 2019-10-09 17:31:01 |
+----+----------+---------+------------------+---------------------+
20 rows in set (0.00 sec)

mysql>

 

 结果:并发请求20次,由于代码和数据库没有控制并发,导致数据错误。

 

四:解决方案

1、加行锁

在查询剩余库存的时候调整sql为
// 查询剩余库存
let querySql = `SELECT
    t.inventory_total,
    t.inventory_remain 
FROM
    db_stock t 
WHERE
    1 = 1 
    AND t.goods_id = ? for update`;

 这个是基于数据库的行锁实现,对于单节点Mysql实现没有难度,测试结果就不放了。

2、使用redis分布式锁(基于Redlock的实现)

调整model.js

let db = require("../utils/mysql_util").db;
let moment = require("moment");
let {async_redis_client, redlock} = require("../utils/redis_util"); let model = {};

/** * 测试并发 * @param reqParams * @returns {Promise<{msg: string, success: boolean}>} */ model.testConcurrentOrder = async function (reqParams) { // 之前本人有个误区,设置资源锁的key为 `goods_${inventory_remain}`,但是仍然有写入错误的数据。 // 思考了一下,觉得是很多请求同时进来,从加锁->释放锁相当于进入队列,前一个操作完成后释放了锁,后一个才要开始加锁。 // 假如有100个请求同时进来,其中50个请求在加锁之前查询到的库存都是100,提交事务的出库信息都是剩余库存99,那么有49条数据都会形成脏数据。 // 所以这里必须设置资源锁的key一致,只要有一个资源还没有释放锁,其他资源就不允许进行查询库存的操作,问题解决。 // 上锁的资源 let resource_lock_key = `redislock_goods`; // 上锁资源5s,操作完成或者到时间后自动释放 let resource_ttl = 5 * 1000; // 开始加锁 let lock = await redlock.lock(resource_lock_key, resource_ttl); console.log(resource_lock_key, lock.value); console.log(moment().format('YYYY-MM-DD HH:mm:ss:SSS')); let initDB = !!reqParams.initDB || 0;// 是否初始化db let goods_id = reqParams.goods_id || "goods_01";// 商品id let opt_num = reqParams.opt_num || 1;// 出库数量 let result = {}; if (initDB) { // 清除数据 await db.query(`TRUNCATE db_stock`); await db.query(`TRUNCATE db_checkout`); // 插入库存数据 await db.query(`INSERT INTO db_stock(goods_id, inventory_total, inventory_remain) VALUES ('${goods_id}', 100, 100)`); result = { success: true, msg: '初始化DB成功', }; } else { let querySql = `SELECT t.inventory_total, t.inventory_remain FROM db_stock t WHERE 1 = 1 AND t.goods_id = ?`; let queryResult = await db.queryOne(querySql, [goods_id]); if (!queryResult) { result = { success: false, msg: `无法匹配商品id:${goods_id}` } } else { // 当前库存 let inventory_remain = queryResult.inventory_remain;
// 等待设置的新库存 let inventory_remain_new
= inventory_remain - 1; let tran = await db.beginTransaction(); try { // 新增出库记录 let checkoutSql = `INSERT INTO db_checkout(goods_id, opt_num, inventory_remain, create_time) VALUES (?, ?, ?, ?)`; await tran.query(checkoutSql, [goods_id, opt_num, inventory_remain_new, new Date()]); // 更新库存信息 let updateStockSql = `UPDATE db_stock t SET inventory_remain = ? WHERE t.goods_id = ?`; // 更新库存 await tran.query(updateStockSql, [inventory_remain_new, goods_id]); await tran.commit(); result = { success: true, msg: '出库成功', } } catch (e) { await tran.rollback(); // 抛出异常之前,及时释放资源锁 await lock.unlock(); throw e; } } } if (lock) { // 及时释放资源锁 await lock.unlock(); } return result; }; module.exports = model;

 

 附上redis_util.js的代码

// Redis单节点
let date_util = require("../utils/date_util");
let RedLock = require("redlock");
// redis
let redis = require("redis");
// async_redis
let async_redis = require("async-redis");

// redis_option
let redis_option = {
    //redis服务器的ip地址
    host: '127.0.0.1',
    //redis服务器的端口
    port: '6379',
    // 授权密码,修改redis.windows.conf requirepass 1234,密码错误报错: NOAUTH Authentication required
    password: '1234',
    // redis select $index,默认0
    db: '0',
    // reply number => string 默认null
    string_numbers: null,
    // replay strings => buffer,默认false
    return_buffers: false,
    // 是否检测缓冲区,默认false
    detect_buffers: false,
    // 长链接,默认true
    socket_keepalive: true,
    // 长链接延迟时间,默认0
    socket_initialdelay: 0,
    // 禁用ready检查,默认false
    no_ready_check: false,
    // 启用离线命令队列,默认true
    enable_offline_queue: true,
    // // @Deprecated 重连最大延迟,默认null
    // retry_max_delay: null,
    // // @Deprecated 连接超时时间,默认60*60*1000,默认1h
    // connect_timeout: 60 * 60 * 1000,
    // // @Deprecated 最大连接次数,默认0,设置为1将阻止任何重新连接尝试
    // max_attempts: 0,
    // 默认false, 如果设置为true,则在重新建立连接后,将重试连接丢失时未执行的所有命令。如果使用状态更改命令(例如incr),请小心使用此命令。这在使用阻塞命令时特别有用。
    retry_unfulfilled_commands: false,
    retry_strategy: function (options) {
        if (options.error && options.error.code === 'ECONNREFUSED') {
            // 拒绝连接
            let errMsg = 'The server refused the connection';
            console.log(date_util.format(), 'retry_strategy', errMsg);
            return new Error(errMsg);
        }
        // 默认 60*60*1000
        if (options.total_retry_time > 60 * 60 * 1000) {
            // 超过最大重试时间
            let errMsg = 'Retry time exhausted';
            console.log(date_util.format(), 'retry_strategy', errMsg);
            return new Error(errMsg);
        }
        // 默认10
        if (options.attempt > 10) {
            // 超过最大重试次数
            let errMsg = 'maximum connection attempts exceeded';
            console.log(date_util.format(), 'retry_strategy', errMsg);
            return new Error(errMsg);
        }
        // reconnect after
        return Math.min(options.attempt * 1000, 3000);
    }
};

// redis_client
let redis_client = redis.createClient(redis_option);

// async_redis_client
let async_redis_client = async_redis.createClient(redis_option);

redis_client.on("error", function (err) {
    console.log(date_util.format(date_util.pattern().ymdhmsSSS), err.toString());
});
async_redis_client.on("error", function (err) {
    console.log(date_util.format(date_util.pattern().ymdhmsSSS), err.toString());
});

// NOTE: 这里只能使用redis_client
let redlock = new RedLock([redis_client]);
redlock.on('clientError', function (err) {
    console.error(date_util.format(date_util.pattern().ymdhmsSSS), 'A redis error has occurred:', err);
});

module.exports = {
    redis_client,
    async_redis_client,
    redlock
};

 ab测试

C:\WINDOWS\system32>ab.exe -n 95 -c 95  http://localhost:3000/dm/testConcurrentOrder
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:
Server Hostname:        localhost
Server Port:            3000

Document Path:          /dm/testConcurrentOrder
Document Length:        46 bytes

Concurrency Level:      95
Time taken for tests:   1.867 seconds
Complete requests:      95
Failed requests:        0
Total transferred:      17860 bytes
HTML transferred:       4370 bytes
Requests per second:    50.88 [#/sec] (mean)
Time per request:       1867.041 [ms] (mean)
Time per request:       19.653 [ms] (mean, across all concurrent requests)
Transfer rate:          9.34 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:    29  932 437.8    909    1840
Waiting:       21  931 437.9    908    1839
Total:         29  932 437.8    909    1840

Percentage of the requests served within a certain time (ms)
  50%    907
  66%   1123
  75%   1257
  80%   1346
  90%   1578
  95%   1727
  98%   1783
  99%   1840
 100%   1840 (longest request)

C:\WINDOWS\system32>

 

Mysql查询结果:

mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
|  1 | goods_01 |             100 |                5 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec)

mysql> select * from db_checkout;
+----+----------+---------+------------------+---------------------+
| id | goods_id | opt_num | inventory_remain | create_time         |
+----+----------+---------+------------------+---------------------+
|  1 | goods_01 |       1 |               99 | 2019-10-10 18:36:15 |
|  2 | goods_01 |       1 |               98 | 2019-10-10 18:36:15 |
|  3 | goods_01 |       1 |               97 | 2019-10-10 18:36:15 |
|  4 | goods_01 |       1 |               96 | 2019-10-10 18:36:15 |
|  5 | goods_01 |       1 |               95 | 2019-10-10 18:36:15 |
|  6 | goods_01 |       1 |               94 | 2019-10-10 18:36:15 |
|  7 | goods_01 |       1 |               93 | 2019-10-10 18:36:15 |
|  8 | goods_01 |       1 |               92 | 2019-10-10 18:36:15 |
|  9 | goods_01 |       1 |               91 | 2019-10-10 18:36:15 |
| 10 | goods_01 |       1 |               90 | 2019-10-10 18:36:15 |
| 11 | goods_01 |       1 |               89 | 2019-10-10 18:36:15 |
| 12 | goods_01 |       1 |               88 | 2019-10-10 18:36:15 |
| 13 | goods_01 |       1 |               87 | 2019-10-10 18:36:15 |
| 14 | goods_01 |       1 |               86 | 2019-10-10 18:36:15 |
| 15 | goods_01 |       1 |               85 | 2019-10-10 18:36:15 |
| 16 | goods_01 |       1 |               84 | 2019-10-10 18:36:15 |
| 17 | goods_01 |       1 |               83 | 2019-10-10 18:36:15 |
| 18 | goods_01 |       1 |               82 | 2019-10-10 18:36:15 |
| 19 | goods_01 |       1 |               81 | 2019-10-10 18:36:15 |
| 20 | goods_01 |       1 |               80 | 2019-10-10 18:36:15 |
| 21 | goods_01 |       1 |               79 | 2019-10-10 18:36:15 |
| 22 | goods_01 |       1 |               78 | 2019-10-10 18:36:15 |
| 23 | goods_01 |       1 |               77 | 2019-10-10 18:36:15 |
| 24 | goods_01 |       1 |               76 | 2019-10-10 18:36:15 |
| 25 | goods_01 |       1 |               75 | 2019-10-10 18:36:15 |
| 26 | goods_01 |       1 |               74 | 2019-10-10 18:36:15 |
| 27 | goods_01 |       1 |               73 | 2019-10-10 18:36:15 |
| 28 | goods_01 |       1 |               72 | 2019-10-10 18:36:15 |
| 29 | goods_01 |       1 |               71 | 2019-10-10 18:36:15 |
| 30 | goods_01 |       1 |               70 | 2019-10-10 18:36:15 |
| 31 | goods_01 |       1 |               69 | 2019-10-10 18:36:15 |
| 32 | goods_01 |       1 |               68 | 2019-10-10 18:36:15 |
| 33 | goods_01 |       1 |               67 | 2019-10-10 18:36:15 |
| 34 | goods_01 |       1 |               66 | 2019-10-10 18:36:15 |
| 35 | goods_01 |       1 |               65 | 2019-10-10 18:36:15 |
| 36 | goods_01 |       1 |               64 | 2019-10-10 18:36:15 |
| 37 | goods_01 |       1 |               63 | 2019-10-10 18:36:15 |
| 38 | goods_01 |       1 |               62 | 2019-10-10 18:36:15 |
| 39 | goods_01 |       1 |               61 | 2019-10-10 18:36:15 |
| 40 | goods_01 |       1 |               60 | 2019-10-10 18:36:15 |
| 41 | goods_01 |       1 |               59 | 2019-10-10 18:36:15 |
| 42 | goods_01 |       1 |               58 | 2019-10-10 18:36:15 |
| 43 | goods_01 |       1 |               57 | 2019-10-10 18:36:15 |
| 44 | goods_01 |       1 |               56 | 2019-10-10 18:36:15 |
| 45 | goods_01 |       1 |               55 | 2019-10-10 18:36:15 |
| 46 | goods_01 |       1 |               54 | 2019-10-10 18:36:16 |
| 47 | goods_01 |       1 |               53 | 2019-10-10 18:36:16 |
| 48 | goods_01 |       1 |               52 | 2019-10-10 18:36:16 |
| 49 | goods_01 |       1 |               51 | 2019-10-10 18:36:16 |
| 50 | goods_01 |       1 |               50 | 2019-10-10 18:36:16 |
| 51 | goods_01 |       1 |               49 | 2019-10-10 18:36:16 |
| 52 | goods_01 |       1 |               48 | 2019-10-10 18:36:16 |
| 53 | goods_01 |       1 |               47 | 2019-10-10 18:36:16 |
| 54 | goods_01 |       1 |               46 | 2019-10-10 18:36:16 |
| 55 | goods_01 |       1 |               45 | 2019-10-10 18:36:16 |
| 56 | goods_01 |       1 |               44 | 2019-10-10 18:36:16 |
| 57 | goods_01 |       1 |               43 | 2019-10-10 18:36:16 |
| 58 | goods_01 |       1 |               42 | 2019-10-10 18:36:16 |
| 59 | goods_01 |       1 |               41 | 2019-10-10 18:36:16 |
| 60 | goods_01 |       1 |               40 | 2019-10-10 18:36:16 |
| 61 | goods_01 |       1 |               39 | 2019-10-10 18:36:16 |
| 62 | goods_01 |       1 |               38 | 2019-10-10 18:36:16 |
| 63 | goods_01 |       1 |               37 | 2019-10-10 18:36:16 |
| 64 | goods_01 |       1 |               36 | 2019-10-10 18:36:16 |
| 65 | goods_01 |       1 |               35 | 2019-10-10 18:36:16 |
| 66 | goods_01 |       1 |               34 | 2019-10-10 18:36:16 |
| 67 | goods_01 |       1 |               33 | 2019-10-10 18:36:16 |
| 68 | goods_01 |       1 |               32 | 2019-10-10 18:36:16 |
| 69 | goods_01 |       1 |               31 | 2019-10-10 18:36:16 |
| 70 | goods_01 |       1 |               30 | 2019-10-10 18:36:16 |
| 71 | goods_01 |       1 |               29 | 2019-10-10 18:36:16 |
| 72 | goods_01 |       1 |               28 | 2019-10-10 18:36:16 |
| 73 | goods_01 |       1 |               27 | 2019-10-10 18:36:16 |
| 74 | goods_01 |       1 |               26 | 2019-10-10 18:36:16 |
| 75 | goods_01 |       1 |               25 | 2019-10-10 18:36:16 |
| 76 | goods_01 |       1 |               24 | 2019-10-10 18:36:16 |
| 77 | goods_01 |       1 |               23 | 2019-10-10 18:36:16 |
| 78 | goods_01 |       1 |               22 | 2019-10-10 18:36:16 |
| 79 | goods_01 |       1 |               21 | 2019-10-10 18:36:16 |
| 80 | goods_01 |       1 |               20 | 2019-10-10 18:36:16 |
| 81 | goods_01 |       1 |               19 | 2019-10-10 18:36:16 |
| 82 | goods_01 |       1 |               18 | 2019-10-10 18:36:16 |
| 83 | goods_01 |       1 |               17 | 2019-10-10 18:36:16 |
| 84 | goods_01 |       1 |               16 | 2019-10-10 18:36:16 |
| 85 | goods_01 |       1 |               15 | 2019-10-10 18:36:16 |
| 86 | goods_01 |       1 |               14 | 2019-10-10 18:36:16 |
| 87 | goods_01 |       1 |               13 | 2019-10-10 18:36:16 |
| 88 | goods_01 |       1 |               12 | 2019-10-10 18:36:16 |
| 89 | goods_01 |       1 |               11 | 2019-10-10 18:36:16 |
| 90 | goods_01 |       1 |               10 | 2019-10-10 18:36:16 |
| 91 | goods_01 |       1 |                9 | 2019-10-10 18:36:16 |
| 92 | goods_01 |       1 |                8 | 2019-10-10 18:36:16 |
| 93 | goods_01 |       1 |                7 | 2019-10-10 18:36:16 |
| 94 | goods_01 |       1 |                6 | 2019-10-10 18:36:16 |
| 95 | goods_01 |       1 |                5 | 2019-10-10 18:36:16 |
+----+----------+---------+------------------+---------------------+
95 rows in set (0.00 sec)

mysql>

完美!

posted @ 2019-10-10 18:44  Ember24  阅读(2857)  评论(0编辑  收藏  举报