Autor Zpráva
JanMatoušek
Profil
Ahoj,
vymýšlím si session storage v databázi. To nese jednu potřebu a to tu, že si potřebuji zamykat řádek.
Vymyslel jsem si, tedy funkci, která se o to bude starat. Vše by bylo v pohodě, ale narazil jsem v poslední části na problém.

V případě, že je session zamčená, tak funkce zkouší určitý čas počkat, jestli se náhodou session neodemkne.

Jediný problém je právě v tom, že i když v db ručně session odemknu (změním locked na null), tak se to v tom cyklu (řádek 34) nepozná a stále to získává hodnotu, že je zamčená...

Zkoušel jsem k tomu ověřovacímu dotazu zadat SQL_NO_CACHE, ale nepomohlo. Nevíte někdo co s tím?

tabulka session:
CREATE TABLE `session` (
  `id` varchar(255) NOT NULL,
  `data` text NOT NULL,
  `createdTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `locked` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Funkce:
DELIMITER ;;
CREATE FUNCTION `getSession` (`sessionId` varchar(255) CHARACTER SET 'utf8', `waitingTime` int) RETURNS text CHARACTER SET 'utf8'
BEGIN

    DECLARE lockedSession TIMESTAMP;
    DECLARE lockExpire int;
    DECLARE result text;
    DECLARE i float;
    DECLARE sleepInterval float;
    DECLARE sleepStatus int;
    DECLARE isExist int;


    SET i = 0;
    SET sleepInterval = 0.2; -- seconds --
    SET lockExpire = 60; -- seconds --


    SELECT data, locked, count(*) into result, lockedSession, isExist FROM session WHERE id = sessionId;


    IF( isExist = 0) THEN
        RETURN result;
    END IF;


    IF( lockedSession IS NULL || ABS(TIMEDIFF(lockedSession , NOW())) > lockExpire  ) THEN
        UPDATE session SET locked = NOW() WHERE id = sessionId;
        SELECT data into result FROM session WHERE id = sessionId;
        RETURN result;
    END IF;


    WHILE (i < waitingTime) DO
        SELECT SQL_NO_CACHE data, locked, count(*) into result, lockedSession, isExist FROM session WHERE id = sessionId;

        IF(lockedSession IS NULL) then
            RETURN result;
        END IF;

        SELECT SLEEP(sleepInterval) into sleepStatus ;
        SET i = i + 1 * sleepInterval ;
    END WHILE;
 

    RETURN FALSE;

END;;
DELIMITER ;
juriad
Profil
JanMatoušek:
Je to kvůli transakcím.

In InnoDB, all user activity occurs inside a transaction. If autocommit mode is enabled, each SQL statement forms a single transaction on its own. By default, MySQL starts the session for each new connection with autocommit enabled, so MySQL does a commit after each SQL statement if that statement did not return an error.

In terms of the SQL:1992 transaction isolation levels, the default InnoDB level is REPEATABLE READ. InnoDB offers all four transaction isolation levels described by the SQL standard: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE.
http://dev.mysql.com/doc/refman/5.1/en/innodb-transaction-model.html

REPEATABLE READ
This is the default isolation level for InnoDB. For consistent reads, there is an important difference from the READ COMMITTED isolation level: All consistent reads within the same transaction read the snapshot established by the first read. This convention means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other. See Section 14.6.3.3, “Consistent Nonlocking Reads”.

http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_repeatable-read

To je důvod proč ve funkci vidíš stále stejná data. Mimochodem, je hodně blbý nápad mít sleep v sql dotazu.


juriad:
A ještě poznámka k funkci NOW:
NOW() returns a constant time that indicates the time at which the statement began to execute. (Within a stored function or trigger, NOW() returns the time at which the function or triggering statement began to execute.) This differs from the behavior for SYSDATE(), which returns the exact time at which it executes.
JanMatoušek
Profil
aha, to mě nenapadlo

No lepší řešení, než ten sleep mě nenapadá. Tebe jo? Když to nesleepnu a začnu to v cyklu bombardovat, tak mi to přijde horší, nebo tomu tak není?
Šance, že bude zrovna session zamčená je docela malá. A viděl bych to tak, na 30s, že ten dotaz bude čekat a kdyžtak vyhodí false a vyhodím výjimku v session handleru

Nebo to leda řešit na straně PHP, ale myslel jsem ,že na straně MySQL by to bylo lepší.


za sysdate díky, to jsem přesně hledal a nenašel :-)
juriad
Profil
Žádný uživatel nebude čumět 30 sekund na prázdnou stránku. Uvědom si také, že proces v PHP ti pořád „běží“; na hostingu máš dostupných obvykle jen 5 - 10 procesů. Stačí, aby se 10 uživatelů (nebo jeden uživatel v 10 tabech) snažil přistoupit na stránku, která má zamknutou session a zablokuje ti celý web na 30 sekund. Hm.
Řešením je nečekat, vůbec, ani v PHP.

Moc nechápu, proč potřebuješ zamykat session storage; nestačí ti použít transakci, případně zamykání po dobu připojení/transakce? InnoDB takové zamykání umí a umí zajistit, že jeden dotaz bude čekat než druhý uvolní zámek (pasivní čekání).


Podívej se na GET_LOCK, různé úrovně transakcí a http://dev.mysql.com/doc/refman/5.0/en/innodb-lock-modes.html. Skutečně potřebuješ něco zamykat mezi jednotlivými requesty uživatele, nebo jde jen o tvůj způsob zajištění konzistence?
JanMatoušek
Profil
Hmm, to máš pravdu, to je blbost.

Ale ta transakce se podle mě také na to nehodí. Transakci chápu tak, že mám připravená data, která chci nasypat do databáze a v případě failu se mi to rollbackne.

Tady bych otevřel transakci na celý běh scriptu a možná bych ani nic neubdatoval, takže bych to podle mě zasekal také. Protože by každý návštěvník otevřel transakci. a čekalo by se na uzavření, což je až po doběhnutí scriptu.

Na ten getlock jsem se díval, ale to se, podle mě, dostanu do stejné fáze, kde jsem, to řeší to, co mám vyřešené.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: