【问题标题】:Perfect way to use MySQL data updating/storing with no lag and error at updating stats使用 MySQL 数据更新/存储的完美方式,在更新统计信息时没有滞后和错误
【发布时间】:2017-07-30 23:32:24
【问题描述】:

我在加载/存储 MySQL 数据时遇到问题,这是我的代码

public void loadPlayer(Player p) {

if (isPlayerInDataBase(p)) {

    Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), new Runnable() {
        @Override
        public void run() {
            Connection connection = sql.getConnection();
            try {
                PreparedStatement select = connection
                        .prepareStatement("SELECT * FROM `MurderData` WHERE playername='" + p.getName() + "'");
                ResultSet result = select.executeQuery();

                if (getPlayerData(p) != null) {
                    while (result.next()) {

                        getPlayerData(p).setdeaths(result.getInt("deaths"));
                        getPlayerData(p).setkills(result.getInt("kills"));
                        getPlayerData(p).setwins(result.getInt("wins"));
                        getPlayerData(p).setlose(result.getInt("loses"));
                        getPlayerData(p).setscore(result.getInt("score"));
                        getPlayerData(p).setcoins(result.getInt("coins"));
                    }

                    CloseResultSet(result);

                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    });
} else {
    Connection connection = sql.getConnection();
    Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), new Runnable() {
        @Override
        public void run() {
            try {
                PreparedStatement insert = connection.prepareStatement(
                        "INSERT INTO `MurderData` (playername, wins, deaths, loses, kills, coins, score) VALUES (?, ?, ?, ?, ?, ?, ?)");
                insert.setString(1, p.getName());
                insert.setInt(2, 0);
                insert.setInt(3, 0);
                insert.setInt(4, 0);
                insert.setInt(5, 0);
                insert.setInt(6, 0);
                insert.setInt(7, 0);
                insert.executeUpdate();
                ClosePreparedStatement(insert);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

public boolean isPlayerInDataBase(Player p) {
        Connection connection = sql.getConnection();
        try {
            PreparedStatement select = connection
                    .prepareStatement("SELECT * FROM `MurderData` WHERE playername='" + p.getName() + "'");
            ResultSet result = select.executeQuery();
            if (result.next()) {
                CloseResultSet(result);
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

所以基本上问题是,我应该在更新 MySQL 数据后关闭准备好的语句吗?我应该让它有一个延迟,比如在 5 秒后关闭它吗?我可以优化此代码以使其更好吗?

数据库中的播放器可以吗?

这是我的两种关闭方法:

public void CloseResultSet(ResultSet s) {

        new BukkitRunnable() {

            @Override
            public void run() {

                if (s != null) {
                    try {
                        s.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }
        }.runTaskLater(this, 20 * getConfig().getInt("close-sql-statements-after"));
    }

    public void ClosePreparedStatement(PreparedStatement s) {

        new BukkitRunnable() {

            @Override
            public void run() {

                if (s != null) {
                    try {
                        s.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }
        }.runTaskLater(this, 20 * getConfig().getInt("close-sql-statements-after"));

    }

更新:这是第二个问题,第一个问题是小滞后尖峰第二个问题是在第 252 行存储数据

public void setSQLData(Player p, int kills, int deaths, int loses, int wins, int coins, int score) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
            @Override
            public void run() {
                Connection connection = plugin.sql.getConnection();
                try {
                    PreparedStatement insert = connection.prepareStatement(
                            "INSERT INTO `MurderData` (playername, wins, deaths, loses, kills, coins, score) VALUES (?, ?, ?, ?, ?, ?, ?)");
                    insert.setString(1, p.getName());
                    insert.setInt(2, wins);
                    insert.setInt(3, deaths);
                    insert.setInt(4, loses);
                    insert.setInt(5, kills);
                    insert.setInt(6, coins);
                    insert.setInt(7, score);
                    insert.executeUpdate();// error line 252
                    plugin.ClosePreparedStatement(insert);
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            }

        });
    }

     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at java.lang.reflect.Constructor.newInstance(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.Util.handleNewInstance(Util.java:407)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3082)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2968)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3516)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1986)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2140)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2626)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2111)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2407)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2325)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2310)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at me.joseph.murder.api.MurderAPI$1.run(MurderAPI.java:252)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftTask.run(CraftTask.java:71)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:53)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at java.lang.Thread.run(Unknown Source)
[22:31:18] [Craft Scheduler Thread - 71/WARN]: Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2529)
[22:31:18] [Craft Scheduler Thread - 71/WARN]:  at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2979)

【问题讨论】:

  • 不要以这种方式运行它,与 Bukkit 的主线程同步,而是在异步线程中运行它,使其成为后台。它不应该锁定您的服务器,因此不会造成延迟
  • 另外,在isPlayerInDataBase(Player p) 中运行查询,然后如果他在数据库中再次运行完全相同的查询似乎有点多余。只需使用您在检查期间获得的数据即可。
  • 这可能完全不符合实际,但另一种方法(除了 Kerooker 和 Solarflare 提到的两种方法)可以帮助解决延迟问题(在系统资源允许的情况下)缓存最近离开的玩家,所以如果他们很快就会回来(比如重新登录),您不必再次获取他们的数据。现在,如果您有系统资源允许,这只会使事情变得更快,如果没有,那么这可能会使事情变慢。现在缓存是一种常见的做法;但是,我不知道这对于 mysql 或 bukkit/minecraft 有多真实。
  • 基本上,我在这里关注这个教程,spigotmc.org/wiki/asynchronously-working-with-a-database,关于第二条评论,你的意思是我需要检查一次?就像如果玩家在数据库中保存在配置中?,我目前与当前代码没有太多滞后,但主要问题是关闭准备好的语句和结果集导致错误,如关闭结果集后无法保存/加载数据和准备好的统计数据,这就是为什么我使用该方法在 5 秒后关闭它们但仍然会导致错误(不是为我),而是为使用该插件的人。
  • 更新增加了一个问题,我的主要问题是存储数据,第二个问题是小滞后峰值。

标签: mysql bukkit


【解决方案1】:

首先:

据我所知,每次执行查询/更新后,您都会关闭连接。不幸的是,这是非常无能的。最好在插件启动时打开MySQL连接,在插件停止时再次关闭。

我个人是这样弄的:(不代表一定要这样)

连接变量:

private static Connection connection;

连接函数:

    public static void connect(String host, String user, String password, String database) {
    Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), () -> {

        //close Connection if still active
        if (connection != null) {
            close();
        }

        //connect to database host
        try {
            Class.forName("com.mysql.jdbc.Driver");

            connection = DriverManager.getConnection("jdbc:mysql://" + host + "/" + database, user, password);

        } catch (SQLException e) {
          System.out.println(e.getMessage());
        }

    });
}

我更新/写入数据库条目的功能:

    public void Update(final String qry) {
    Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), () -> {
        try {
            Statement stnt = connection.createStatement();
            stnt.executeUpdate(qry);

        } catch (SQLException e) {
            e.printStackTrace();
        }
    });

}

我从数据库中查询信息的功能:

如您所见,此函数不是异步的。不幸的是,我现在还没有管理它来异步获取这个函数。但是您可以通过使函数调用异步来轻松解决此问题。前任: Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), () -> { Query("your query"); });

    public ResultSet Query(String qry) {
    ResultSet rs = null;

    try {
        Statement stnt = connection.createStatement();
        rs = stnt.executeQuery(qry);
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return rs;
}

关闭函数:

    public static void close() {
    Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), () -> {
        try {
            connection.close();
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    });
}

这种方式的一个缺点是您一次只能连接到一个数据库(在我的情况下这很好)。

希望对您有所帮助。我也为奇怪的 MySQL 错误而苦苦挣扎。幸运的是,这段代码一切正常。

回答你的问题:

[22:31:18] [Craft Scheduler Thread - 71/WARN]: Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.

据我所知,这意味着与 MySQL 服务器的连接已关闭,即使插件尝试使用它。 如上所述,在开始时打开连接并保持打开状态直到插件停止应该可以解决此问题。

【讨论】:

    【解决方案2】:

    感谢所有帮助我发现问题出在这条线上的人

     public void setSQLData(Player p, int kills, int deaths, int loses, int wins, int coins, int score) {
    
    
      Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
            @Override
            public void run() {
                Connection connection = plugin.sql.getConnection();
                try {
                    PreparedStatement insert = connection.prepareStatement(
                            "INSERT INTO `MurderData` (playername, wins, deaths, loses, kills, coins, score) VALUES (?, ?, ?, ?, ?, ?, ?)");
    

    应该是UPDATE而不是插入

    【讨论】:

      猜你喜欢
      • 2012-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-10
      相关资源
      最近更新 更多